]> git.proxmox.com Git - pve-installer.git/blob - proxmox-installer-common/src/disk_checks.rs
bump version to 8.2.6
[pve-installer.git] / proxmox-installer-common / src / disk_checks.rs
1 use std::collections::HashSet;
2
3 use crate::options::{BtrfsRaidLevel, Disk, ZfsRaidLevel};
4 use crate::setup::BootType;
5
6 /// Checks a list of disks for duplicate entries, using their index as key.
7 ///
8 /// # Arguments
9 ///
10 /// * `disks` - A list of disks to check for duplicates.
11 pub fn check_for_duplicate_disks(disks: &[Disk]) -> Result<(), &Disk> {
12 let mut set = HashSet::new();
13
14 for disk in disks {
15 if !set.insert(&disk.index) {
16 return Err(disk);
17 }
18 }
19
20 Ok(())
21 }
22
23 /// Simple wrapper which returns an descriptive error if the list of disks is too short.
24 ///
25 /// # Arguments
26 ///
27 /// * `disks` - A list of disks to check the lenght of.
28 /// * `min` - Minimum number of disks
29 pub fn check_raid_min_disks(disks: &[Disk], min: usize) -> Result<(), String> {
30 if disks.len() < min {
31 Err(format!("Need at least {min} disks"))
32 } else {
33 Ok(())
34 }
35 }
36
37 /// Checks all disks for legacy BIOS boot compatibility and reports an error as appropriate. 4Kn
38 /// disks are generally broken with legacy BIOS and cannot be booted from.
39 ///
40 /// # Arguments
41 ///
42 /// * `runinfo` - `RuntimeInfo` instance of currently running system
43 /// * `disks` - List of disks designated as bootdisk targets.
44 pub fn check_disks_4kn_legacy_boot(boot_type: BootType, disks: &[Disk]) -> Result<(), &str> {
45 if boot_type == BootType::Bios && disks.iter().any(|disk| disk.block_size == Some(4096)) {
46 return Err("Booting from 4Kn drive in legacy BIOS mode is not supported.");
47 }
48
49 Ok(())
50 }
51
52 /// Checks whether a user-supplied ZFS RAID setup is valid or not, such as disk sizes andminimum
53 /// number of disks.
54 ///
55 /// # Arguments
56 ///
57 /// * `level` - The targeted ZFS RAID level by the user.
58 /// * `disks` - List of disks designated as RAID targets.
59 pub fn check_zfs_raid_config(level: ZfsRaidLevel, disks: &[Disk]) -> Result<(), String> {
60 // See also Proxmox/Install.pm:get_zfs_raid_setup()
61
62 let check_mirror_size = |disk1: &Disk, disk2: &Disk| {
63 if (disk1.size - disk2.size).abs() > disk1.size / 10. {
64 Err(format!(
65 "Mirrored disks must have same size:\n\n * {disk1}\n * {disk2}"
66 ))
67 } else {
68 Ok(())
69 }
70 };
71
72 match level {
73 ZfsRaidLevel::Raid0 => check_raid_min_disks(disks, 1)?,
74 ZfsRaidLevel::Raid1 => {
75 check_raid_min_disks(disks, 2)?;
76 for disk in disks {
77 check_mirror_size(&disks[0], disk)?;
78 }
79 }
80 ZfsRaidLevel::Raid10 => {
81 check_raid_min_disks(disks, 4)?;
82
83 if disks.len() % 2 != 0 {
84 return Err(format!(
85 "Needs an even number of disks, currently selected: {}",
86 disks.len(),
87 ));
88 }
89
90 // Pairs need to have the same size
91 for i in (0..disks.len()).step_by(2) {
92 check_mirror_size(&disks[i], &disks[i + 1])?;
93 }
94 }
95 // For RAID-Z: minimum disks number is level + 2
96 ZfsRaidLevel::RaidZ => {
97 check_raid_min_disks(disks, 3)?;
98 for disk in disks {
99 check_mirror_size(&disks[0], disk)?;
100 }
101 }
102 ZfsRaidLevel::RaidZ2 => {
103 check_raid_min_disks(disks, 4)?;
104 for disk in disks {
105 check_mirror_size(&disks[0], disk)?;
106 }
107 }
108 ZfsRaidLevel::RaidZ3 => {
109 check_raid_min_disks(disks, 5)?;
110 for disk in disks {
111 check_mirror_size(&disks[0], disk)?;
112 }
113 }
114 }
115
116 Ok(())
117 }
118
119 /// Checks whether a user-supplied Btrfs RAID setup is valid or not, such as minimum
120 /// number of disks.
121 ///
122 /// # Arguments
123 ///
124 /// * `level` - The targeted Btrfs RAID level by the user.
125 /// * `disks` - List of disks designated as RAID targets.
126 pub fn check_btrfs_raid_config(level: BtrfsRaidLevel, disks: &[Disk]) -> Result<(), String> {
127 // See also Proxmox/Install.pm:get_btrfs_raid_setup()
128
129 match level {
130 BtrfsRaidLevel::Raid0 => check_raid_min_disks(disks, 1)?,
131 BtrfsRaidLevel::Raid1 => check_raid_min_disks(disks, 2)?,
132 BtrfsRaidLevel::Raid10 => check_raid_min_disks(disks, 4)?,
133 }
134
135 Ok(())
136 }
137
138 #[cfg(test)]
139 mod tests {
140 use super::*;
141
142 fn dummy_disk(index: usize) -> Disk {
143 Disk {
144 index: index.to_string(),
145 path: format!("/dev/dummy{index}"),
146 model: Some("Dummy disk".to_owned()),
147 size: 1024. * 1024. * 1024. * 8.,
148 block_size: Some(512),
149 }
150 }
151
152 fn dummy_disks(num: usize) -> Vec<Disk> {
153 (0..num).map(dummy_disk).collect()
154 }
155
156 #[test]
157 fn duplicate_disks() {
158 assert!(check_for_duplicate_disks(&dummy_disks(2)).is_ok());
159 assert_eq!(
160 check_for_duplicate_disks(&[
161 dummy_disk(0),
162 dummy_disk(1),
163 dummy_disk(2),
164 dummy_disk(2),
165 dummy_disk(3),
166 ]),
167 Err(&dummy_disk(2)),
168 );
169 }
170
171 #[test]
172 fn raid_min_disks() {
173 let disks = dummy_disks(10);
174
175 assert!(check_raid_min_disks(&disks[..1], 2).is_err());
176 assert!(check_raid_min_disks(&disks[..1], 1).is_ok());
177 assert!(check_raid_min_disks(&disks, 1).is_ok());
178 }
179
180 #[test]
181 fn bios_boot_compat_4kn() {
182 for i in 0..10 {
183 let mut disks = dummy_disks(10);
184 disks[i].block_size = Some(4096);
185
186 // Must fail if /any/ of the disks are 4Kn
187 assert!(check_disks_4kn_legacy_boot(BootType::Bios, &disks).is_err());
188 // For UEFI, we allow it for every configuration
189 assert!(check_disks_4kn_legacy_boot(BootType::Efi, &disks).is_ok());
190 }
191 }
192
193 #[test]
194 fn btrfs_raid() {
195 let disks = dummy_disks(10);
196
197 assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid0, &[]).is_err());
198 assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid0, &disks[..1]).is_ok());
199 assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid0, &disks).is_ok());
200
201 assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid1, &[]).is_err());
202 assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid1, &disks[..1]).is_err());
203 assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid1, &disks[..2]).is_ok());
204 assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid1, &disks).is_ok());
205
206 assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid10, &[]).is_err());
207 assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid10, &disks[..3]).is_err());
208 assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid10, &disks[..4]).is_ok());
209 assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid10, &disks).is_ok());
210 }
211
212 #[test]
213 fn zfs_raid() {
214 let disks = dummy_disks(10);
215
216 assert!(check_zfs_raid_config(ZfsRaidLevel::Raid0, &[]).is_err());
217 assert!(check_zfs_raid_config(ZfsRaidLevel::Raid0, &disks[..1]).is_ok());
218 assert!(check_zfs_raid_config(ZfsRaidLevel::Raid0, &disks).is_ok());
219
220 assert!(check_zfs_raid_config(ZfsRaidLevel::Raid1, &[]).is_err());
221 assert!(check_zfs_raid_config(ZfsRaidLevel::Raid1, &disks[..2]).is_ok());
222 assert!(check_zfs_raid_config(ZfsRaidLevel::Raid1, &disks).is_ok());
223
224 assert!(check_zfs_raid_config(ZfsRaidLevel::Raid10, &[]).is_err());
225 assert!(check_zfs_raid_config(ZfsRaidLevel::Raid10, &dummy_disks(4)).is_ok());
226 assert!(check_zfs_raid_config(ZfsRaidLevel::Raid10, &disks).is_ok());
227
228 assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ, &[]).is_err());
229 assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ, &disks[..2]).is_err());
230 assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ, &disks[..3]).is_ok());
231 assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ, &disks).is_ok());
232
233 assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ2, &[]).is_err());
234 assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ2, &disks[..3]).is_err());
235 assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ2, &disks[..4]).is_ok());
236 assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ2, &disks).is_ok());
237
238 assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ3, &[]).is_err());
239 assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ3, &disks[..4]).is_err());
240 assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ3, &disks[..5]).is_ok());
241 assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ3, &disks).is_ok());
242 }
243 }