petri/vm/
vtl2_settings.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4//! Convenience methods for tests to generate and modify the VTL2 settings of a
5//! VM under test.
6
7use guid::Guid;
8
9/// Logical type of the storage controller, used for both devices presented from
10/// the host to VTL2, and also from VTL2 to the VTL0 guest.
11#[expect(missing_docs)]
12#[derive(Debug, PartialEq, Eq)]
13pub enum ControllerType {
14    Scsi,
15    Ide,
16    Nvme,
17}
18
19/// A builder for a physical device that will back a VTL2 LUN. This is the
20/// storage that is presented from the host OS to VTL2. A single VTL2 lun can
21/// have zero, one, or multiple backing devices (see [`Vtl2LunBuilder`] for more
22/// details on that).
23#[derive(Debug, PartialEq, Eq)]
24pub struct Vtl2StorageBackingDeviceBuilder {
25    device_type: ControllerType,
26    device_path: Guid,
27    sub_device_path: u32,
28}
29
30impl Vtl2StorageBackingDeviceBuilder {
31    /// Creates a new physical device builder.
32    ///
33    /// `device_type` is the type of device presented to VTL2. `device_path` is
34    /// the path to the device. Since both SCSI and NVMe are really just VMBUS
35    /// devices, this is the VMBUS instance id (a.k.a the `vsid`).
36    /// `sub_device_path` is the SCSI target id or NVMe namespace id within that
37    /// device.
38    ///
39    /// IDE is not supported as a VTL2 backing device.
40    pub fn new(device_type: ControllerType, device_path: Guid, sub_device_path: u32) -> Self {
41        assert_ne!(device_type, ControllerType::Ide); // IDE is not supported as VTL2 backing device
42        Self {
43            device_type,
44            device_path,
45            sub_device_path,
46        }
47    }
48
49    /// Builds the physical device into the protobuf type used by VTL2 settings.
50    pub fn build(self) -> vtl2_settings_proto::PhysicalDevice {
51        let device_type = match self.device_type {
52            ControllerType::Scsi => vtl2_settings_proto::physical_device::DeviceType::Vscsi,
53            ControllerType::Nvme => vtl2_settings_proto::physical_device::DeviceType::Nvme,
54            ControllerType::Ide => unreachable!(),
55        };
56        vtl2_settings_proto::PhysicalDevice {
57            device_type: device_type.into(),
58            device_path: self.device_path.to_string(),
59            sub_device_path: self.sub_device_path,
60        }
61    }
62}
63
64/// VTL2 Settings wraps the list of backing devices into a
65/// [`PhysicalDevices`](vtl2_settings_proto::PhysicalDevices) type, with
66/// subtlely different fields depending on whether there is one or multiple
67/// devices. This helper builds that wrapper type.
68pub fn build_vtl2_storage_backing_physical_devices(
69    mut devices: Vec<Vtl2StorageBackingDeviceBuilder>,
70) -> Option<vtl2_settings_proto::PhysicalDevices> {
71    match devices.len() {
72        0 => None,
73        1 => Some(vtl2_settings_proto::PhysicalDevices {
74            r#type: vtl2_settings_proto::physical_devices::BackingType::Single.into(),
75            device: Some(devices.pop().unwrap().build()),
76            devices: Vec::new(),
77        }),
78        _ => Some(vtl2_settings_proto::PhysicalDevices {
79            r#type: vtl2_settings_proto::physical_devices::BackingType::Striped.into(),
80            device: None,
81            devices: devices.drain(..).map(|d| d.build()).collect(),
82        }),
83    }
84}
85
86/// A builder for a VTL2 LUN, which is a storage device presented from VTL2 to
87/// the guest.
88///
89/// A LUN can be one of two flavors: a disk or a DVD (really a virtual optical
90/// device, since it's a CD-ROM when presented over IDE). A DVD can be empty
91/// (backed by no physical devices) or it can be backed by one or more physical
92/// devices. A disk must be backed by one or more physical devices. (This is not
93/// checked, since there may be interesting test cases that need to violate
94/// these requirements.)
95#[derive(Debug, PartialEq, Eq)]
96pub struct Vtl2LunBuilder {
97    channel: Option<u32>,
98    location: u32,
99    device_id: Guid,
100    vendor_id: String,
101    product_id: String,
102    product_revision_level: String,
103    serial_number: String,
104    model_number: String,
105    physical_devices: Vec<Vtl2StorageBackingDeviceBuilder>,
106    is_dvd: bool,
107    chunk_size_in_kb: u32,
108}
109
110impl Vtl2LunBuilder {
111    /// Creates a new disk LUN builder with default values. Here "disk" is as
112    /// opposed to NOT a DVD.
113    pub fn disk() -> Self {
114        Self {
115            channel: None,
116            location: 0,
117            device_id: Guid::new_random(),
118            vendor_id: "OpenVMM".to_string(),
119            product_id: "Disk".to_string(),
120            product_revision_level: "1.0".to_string(),
121            serial_number: "0".to_string(),
122            model_number: "1".to_string(),
123            physical_devices: Vec::new(),
124            is_dvd: false,
125            chunk_size_in_kb: 0,
126        }
127    }
128
129    /// Creates a new dvd LUN builder with default values. Here "dvd" is as
130    /// opposed to NOT a disk.
131    pub fn dvd() -> Self {
132        let mut s = Self::disk();
133        s.is_dvd = true;
134        s.product_id = "DVD".to_string();
135        s
136    }
137
138    /// Guest visible IDE controller number
139    pub fn with_channel(mut self, channel: u32) -> Self {
140        self.channel = Some(channel);
141        self
142    }
143
144    /// Guest visible location of the device (aka a guest "LUN")
145    pub fn with_location(mut self, location: u32) -> Self {
146        self.location = location;
147        self
148    }
149
150    /// The physical devices backing the LUN.
151    ///
152    /// Overwrites any current physical backing device configuration (one or many).
153    pub fn with_physical_devices(
154        mut self,
155        physical_devices: Vec<Vtl2StorageBackingDeviceBuilder>,
156    ) -> Self {
157        self.physical_devices = physical_devices;
158        self
159    }
160
161    /// The single physical device backing the LUN.
162    ///
163    /// Overwrites any current physical backing device configuration (one or many).
164    pub fn with_physical_device(self, physical_device: Vtl2StorageBackingDeviceBuilder) -> Self {
165        self.with_physical_devices(vec![physical_device])
166    }
167
168    /// For striped devices, the size of the stripe chunk in KB.
169    pub fn with_chunk_size_in_kb(mut self, chunk_size_in_kb: u32) -> Self {
170        self.chunk_size_in_kb = chunk_size_in_kb;
171        self
172    }
173
174    /// Builds the LUN into the protobuf type used by VTL2 settings.
175    pub fn build(self) -> vtl2_settings_proto::Lun {
176        vtl2_settings_proto::Lun {
177            channel: self.channel,
178            location: self.location,
179            device_id: self.device_id.to_string(),
180            vendor_id: self.vendor_id,
181            product_id: self.product_id,
182            product_revision_level: self.product_revision_level,
183            serial_number: self.serial_number,
184            model_number: self.model_number,
185            physical_devices: build_vtl2_storage_backing_physical_devices(self.physical_devices),
186            is_dvd: self.is_dvd,
187            chunk_size_in_kb: self.chunk_size_in_kb,
188            ..Default::default()
189        }
190    }
191}
192
193/// A builder for a VTL2 storage controller, which presents one or more LUNs to
194/// the guest.
195///
196/// The controller has a type (SCSI, IDE, or NVMe). For SCSI controllers, the
197/// guest identifies the controller by the supplied instance_id (a GUID). A
198/// single disk can be located by its LUN on a specific controller.
199#[derive(Debug, PartialEq, Eq)]
200pub struct Vtl2StorageControllerBuilder {
201    instance_id: Guid,
202    protocol: ControllerType,
203    luns: Vec<Vtl2LunBuilder>,
204    io_queue_depth: Option<u32>,
205}
206
207impl Vtl2StorageControllerBuilder {
208    /// Creates a new storage controller builder with default values
209    /// (arbitrary `instance_id` and no disks) and the specified guest-visible
210    /// controller type.
211    pub fn new(protocol: ControllerType) -> Self {
212        Self {
213            instance_id: Guid::new_random(),
214            protocol,
215            luns: Vec::new(),
216            io_queue_depth: None,
217        }
218    }
219
220    /// Set the guest-visible instance GUID.
221    pub fn with_instance_id(mut self, instance_id: Guid) -> Self {
222        self.instance_id = instance_id;
223        self
224    }
225
226    /// Add a LUN to the controller.
227    pub fn add_lun(mut self, lun: Vtl2LunBuilder) -> Self {
228        self.luns.push(lun);
229        self
230    }
231
232    /// Add a LUN to the controller.
233    pub fn add_luns(mut self, luns: Vec<Vtl2LunBuilder>) -> Self {
234        self.luns.extend(luns);
235        self
236    }
237
238    /// Generate the VTL2 settings for the controller.
239    pub fn build(mut self) -> vtl2_settings_proto::StorageController {
240        let protocol = match self.protocol {
241            ControllerType::Scsi => vtl2_settings_proto::storage_controller::StorageProtocol::Scsi,
242            ControllerType::Nvme => vtl2_settings_proto::storage_controller::StorageProtocol::Nvme,
243            ControllerType::Ide => vtl2_settings_proto::storage_controller::StorageProtocol::Ide,
244        };
245
246        vtl2_settings_proto::StorageController {
247            instance_id: self.instance_id.to_string(),
248            protocol: protocol.into(),
249            luns: self.luns.drain(..).map(|l| l.build()).collect(),
250            io_queue_depth: self.io_queue_depth,
251        }
252    }
253}