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    location: u32,
98    device_id: Guid,
99    vendor_id: String,
100    product_id: String,
101    product_revision_level: String,
102    serial_number: String,
103    model_number: String,
104    physical_devices: Vec<Vtl2StorageBackingDeviceBuilder>,
105    is_dvd: bool,
106    chunk_size_in_kb: u32,
107}
108
109impl Vtl2LunBuilder {
110    /// Creates a new disk LUN builder with default values. Here "disk" is as
111    /// opposed to NOT a DVD.
112    pub fn disk() -> Self {
113        Self {
114            location: 0,
115            device_id: Guid::new_random(),
116            vendor_id: "OpenVMM".to_string(),
117            product_id: "Disk".to_string(),
118            product_revision_level: "1.0".to_string(),
119            serial_number: "0".to_string(),
120            model_number: "1".to_string(),
121            physical_devices: Vec::new(),
122            is_dvd: false,
123            chunk_size_in_kb: 0,
124        }
125    }
126
127    /// Creates a new dvd LUN builder with default values. Here "dvd" is as
128    /// opposed to NOT a disk.
129    pub fn dvd() -> Self {
130        let mut s = Self::disk();
131        s.is_dvd = true;
132        s.product_id = "DVD".to_string();
133        s
134    }
135
136    /// Guest visible location of the device (aka a guest "LUN")
137    pub fn with_location(mut self, location: u32) -> Self {
138        self.location = location;
139        self
140    }
141
142    /// The physical devices backing the LUN.
143    ///
144    /// Overwrites any current physical backing device configuration (one or many).
145    pub fn with_physical_devices(
146        mut self,
147        physical_devices: Vec<Vtl2StorageBackingDeviceBuilder>,
148    ) -> Self {
149        self.physical_devices = physical_devices;
150        self
151    }
152
153    /// The single physical device backing the LUN.
154    ///
155    /// Overwrites any current physical backing device configuration (one or many).
156    pub fn with_physical_device(self, physical_device: Vtl2StorageBackingDeviceBuilder) -> Self {
157        self.with_physical_devices(vec![physical_device])
158    }
159
160    /// For striped devices, the size of the stripe chunk in KB.
161    pub fn with_chunk_size_in_kb(mut self, chunk_size_in_kb: u32) -> Self {
162        self.chunk_size_in_kb = chunk_size_in_kb;
163        self
164    }
165
166    /// Builds the LUN into the protobuf type used by VTL2 settings.
167    pub fn build(self) -> vtl2_settings_proto::Lun {
168        vtl2_settings_proto::Lun {
169            location: self.location,
170            device_id: self.device_id.to_string(),
171            vendor_id: self.vendor_id,
172            product_id: self.product_id,
173            product_revision_level: self.product_revision_level,
174            serial_number: self.serial_number,
175            model_number: self.model_number,
176            physical_devices: build_vtl2_storage_backing_physical_devices(self.physical_devices),
177            is_dvd: self.is_dvd,
178            chunk_size_in_kb: self.chunk_size_in_kb,
179            ..Default::default()
180        }
181    }
182}
183
184/// A builder for a VTL2 storage controller, which presents one or more LUNs to
185/// the guest.
186///
187/// The controller has a type (SCSI, IDE, or NVMe). For SCSI controllers, the
188/// guest identifies the controller by the supplied instance_id (a GUID). A
189/// single disk can be located by its LUN on a specific controller.
190#[derive(Debug, PartialEq, Eq)]
191pub struct Vtl2StorageControllerBuilder {
192    instance_id: Guid,
193    protocol: ControllerType,
194    luns: Vec<Vtl2LunBuilder>,
195    io_queue_depth: Option<u32>,
196}
197
198impl Vtl2StorageControllerBuilder {
199    /// Creates a new storage controller builder with default values (a SCSI
200    /// controller, with arbitrary `instance_id` and no disks).
201    pub fn scsi() -> Self {
202        Self {
203            instance_id: Guid::new_random(),
204            protocol: ControllerType::Scsi,
205            luns: Vec::new(),
206            io_queue_depth: None,
207        }
208    }
209
210    /// Set the guest-visible instance GUID.
211    pub fn with_instance_id(mut self, instance_id: Guid) -> Self {
212        self.instance_id = instance_id;
213        self
214    }
215
216    /// Change the guest-visible protocol.
217    pub fn with_protocol(mut self, protocol: ControllerType) -> Self {
218        self.protocol = protocol;
219        self
220    }
221
222    /// Add a LUN to the controller.
223    pub fn add_lun(mut self, lun: Vtl2LunBuilder) -> Self {
224        self.luns.push(lun);
225        self
226    }
227
228    /// Add a LUN to the controller.
229    pub fn add_luns(mut self, luns: Vec<Vtl2LunBuilder>) -> Self {
230        self.luns.extend(luns);
231        self
232    }
233
234    /// Generate the VTL2 settings for the controller.
235    pub fn build(mut self) -> vtl2_settings_proto::StorageController {
236        let protocol = match self.protocol {
237            ControllerType::Scsi => vtl2_settings_proto::storage_controller::StorageProtocol::Scsi,
238            ControllerType::Nvme => vtl2_settings_proto::storage_controller::StorageProtocol::Nvme,
239            ControllerType::Ide => vtl2_settings_proto::storage_controller::StorageProtocol::Ide,
240        };
241
242        vtl2_settings_proto::StorageController {
243            instance_id: self.instance_id.to_string(),
244            protocol: protocol.into(),
245            luns: self.luns.drain(..).map(|l| l.build()).collect(),
246            io_queue_depth: self.io_queue_depth,
247        }
248    }
249}