]> git.proxmox.com Git - pve-installer.git/blob - proxmox-installer-common/src/disk_checks.rs
html: pbs: fix missing <br> in template after feature list
[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 let is_blocksize_4096 = |disk: &Disk| disk.block_size.map(|s| s == 4096).unwrap_or(false);
46
47 if boot_type == BootType::Bios && disks.iter().any(is_blocksize_4096) {
48 return Err("Booting from 4Kn drive in legacy BIOS mode is not supported.");
49 }
50
51 Ok(())
52 }
53
54 /// Checks whether a user-supplied ZFS RAID setup is valid or not, such as disk sizes andminimum
55 /// number of disks.
56 ///
57 /// # Arguments
58 ///
59 /// * `level` - The targeted ZFS RAID level by the user.
60 /// * `disks` - List of disks designated as RAID targets.
61 pub fn check_zfs_raid_config(level: ZfsRaidLevel, disks: &[Disk]) -> Result<(), String> {
62 // See also Proxmox/Install.pm:get_zfs_raid_setup()
63
64 let check_mirror_size = |disk1: &Disk, disk2: &Disk| {
65 if (disk1.size - disk2.size).abs() > disk1.size / 10. {
66 Err(format!(
67 "Mirrored disks must have same size:\n\n * {disk1}\n * {disk2}"
68 ))
69 } else {
70 Ok(())
71 }
72 };
73
74 match level {
75 ZfsRaidLevel::Raid0 => check_raid_min_disks(disks, 1)?,
76 ZfsRaidLevel::Raid1 => {
77 check_raid_min_disks(disks, 2)?;
78 for disk in disks {
79 check_mirror_size(&disks[0], disk)?;
80 }
81 }
82 ZfsRaidLevel::Raid10 => {
83 check_raid_min_disks(disks, 4)?;
84
85 if disks.len() % 2 != 0 {
86 return Err(format!(
87 "Needs an even number of disks, currently selected: {}",
88 disks.len(),
89 ));
90 }
91
92 // Pairs need to have the same size
93 for i in (0..disks.len()).step_by(2) {
94 check_mirror_size(&disks[i], &disks[i + 1])?;
95 }
96 }
97 // For RAID-Z: minimum disks number is level + 2
98 ZfsRaidLevel::RaidZ => {
99 check_raid_min_disks(disks, 3)?;
100 for disk in disks {
101 check_mirror_size(&disks[0], disk)?;
102 }
103 }
104 ZfsRaidLevel::RaidZ2 => {
105 check_raid_min_disks(disks, 4)?;
106 for disk in disks {
107 check_mirror_size(&disks[0], disk)?;
108 }
109 }
110 ZfsRaidLevel::RaidZ3 => {
111 check_raid_min_disks(disks, 5)?;
112 for disk in disks {
113 check_mirror_size(&disks[0], disk)?;
114 }
115 }
116 }
117
118 Ok(())
119 }
120
121 /// Checks whether a user-supplied Btrfs RAID setup is valid or not, such as minimum
122 /// number of disks.
123 ///
124 /// # Arguments
125 ///
126 /// * `level` - The targeted Btrfs RAID level by the user.
127 /// * `disks` - List of disks designated as RAID targets.
128 pub fn check_btrfs_raid_config(level: BtrfsRaidLevel, disks: &[Disk]) -> Result<(), String> {
129 // See also Proxmox/Install.pm:get_btrfs_raid_setup()
130
131 match level {
132 BtrfsRaidLevel::Raid0 => check_raid_min_disks(disks, 1)?,
133 BtrfsRaidLevel::Raid1 => check_raid_min_disks(disks, 2)?,
134 BtrfsRaidLevel::Raid10 => check_raid_min_disks(disks, 4)?,
135 }
136
137 Ok(())
138 }
139
140 #[cfg(test)]
141 mod tests {
142 use super::*;
143
144 fn dummy_disk(index: usize) -> Disk {
145 Disk {
146 index: index.to_string(),
147 path: format!("/dev/dummy{index}"),
148 model: Some("Dummy disk".to_owned()),
149 size: 1024. * 1024. * 1024. * 8.,
150 block_size: Some(512),
151 }
152 }
153
154 fn dummy_disks(num: usize) -> Vec<Disk> {
155 (0..num).map(dummy_disk).collect()
156 }
157
158 #[test]
159 fn duplicate_disks() {
160 assert!(check_for_duplicate_disks(&dummy_disks(2)).is_ok());
161 assert_eq!(
162 check_for_duplicate_disks(&[
163 dummy_disk(0),
164 dummy_disk(1),
165 dummy_disk(2),
166 dummy_disk(2),
167 dummy_disk(3),
168 ]),
169 Err(&dummy_disk(2)),
170 );
171 }
172
173 #[test]
174 fn raid_min_disks() {
175 let disks = dummy_disks(10);
176
177 assert!(check_raid_min_disks(&disks[..1], 2).is_err());
178 assert!(check_raid_min_disks(&disks[..1], 1).is_ok());
179 assert!(check_raid_min_disks(&disks, 1).is_ok());
180 }
181
182 #[test]
183 fn bios_boot_compat_4kn() {
184 for i in 0..10 {
185 let mut disks = dummy_disks(10);
186 disks[i].block_size = Some(4096);
187
188 // Must fail if /any/ of the disks are 4Kn
189 assert!(check_disks_4kn_legacy_boot(BootType::Bios, &disks).is_err());
190 // For UEFI, we allow it for every configuration
191 assert!(check_disks_4kn_legacy_boot(BootType::Efi, &disks).is_ok());
192 }
193 }
194
195 #[test]
196 fn btrfs_raid() {
197 let disks = dummy_disks(10);
198
199 assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid0, &[]).is_err());
200 assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid0, &disks[..1]).is_ok());
201 assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid0, &disks).is_ok());
202
203 assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid1, &[]).is_err());
204 assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid1, &disks[..1]).is_err());
205 assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid1, &disks[..2]).is_ok());
206 assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid1, &disks).is_ok());
207
208 assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid10, &[]).is_err());
209 assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid10, &disks[..3]).is_err());
210 assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid10, &disks[..4]).is_ok());
211 assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid10, &disks).is_ok());
212 }
213
214 #[test]
215 fn zfs_raid() {
216 let disks = dummy_disks(10);
217
218 assert!(check_zfs_raid_config(ZfsRaidLevel::Raid0, &[]).is_err());
219 assert!(check_zfs_raid_config(ZfsRaidLevel::Raid0, &disks[..1]).is_ok());
220 assert!(check_zfs_raid_config(ZfsRaidLevel::Raid0, &disks).is_ok());
221
222 assert!(check_zfs_raid_config(ZfsRaidLevel::Raid1, &[]).is_err());
223 assert!(check_zfs_raid_config(ZfsRaidLevel::Raid1, &disks[..2]).is_ok());
224 assert!(check_zfs_raid_config(ZfsRaidLevel::Raid1, &disks).is_ok());
225
226 assert!(check_zfs_raid_config(ZfsRaidLevel::Raid10, &[]).is_err());
227 assert!(check_zfs_raid_config(ZfsRaidLevel::Raid10, &dummy_disks(4)).is_ok());
228 assert!(check_zfs_raid_config(ZfsRaidLevel::Raid10, &disks).is_ok());
229
230 assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ, &[]).is_err());
231 assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ, &disks[..2]).is_err());
232 assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ, &disks[..3]).is_ok());
233 assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ, &disks).is_ok());
234
235 assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ2, &[]).is_err());
236 assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ2, &disks[..3]).is_err());
237 assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ2, &disks[..4]).is_ok());
238 assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ2, &disks).is_ok());
239
240 assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ3, &[]).is_err());
241 assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ3, &disks[..4]).is_err());
242 assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ3, &disks[..5]).is_ok());
243 assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ3, &disks).is_ok());
244 }
245 }