bsudlib/
drive.rs

1use crate::bsu::Bsu;
2use crate::config::{self, Config, ConfigFileDrive, DriveTarget, VM_ID};
3use crate::fs;
4use crate::lvm;
5use crate::utils::{bytes_to_gib, bytes_to_gib_rounded, gib_to_bytes};
6use datetime::{Duration, Instant};
7use easy_error::format_err;
8use log::info;
9use log::{debug, error};
10use std::cmp::Ordering;
11use std::cmp::{max, min};
12use std::collections::{HashMap, HashSet};
13use std::error::Error;
14use std::path::Path;
15use std::sync::mpsc::{channel, Receiver, Sender};
16use std::thread::sleep;
17use std::time;
18use threadpool::ThreadPool;
19
20const RECONCILE_COOLDOWN_S: u64 = 30;
21const DEFAULT_INITIAL_DISK_GIB: usize = 10;
22const DEFAULT_MAX_DISKS: usize = 10;
23const DEFAULT_MAX_USED_PERC: usize = 85;
24const DEFAULT_MIN_USED_PERC: usize = 40;
25const DEFAULT_SCALE_FACTOR_PERC: usize = 20;
26const DEFAULT_DISK_TYPE: config::DiskType = config::DiskType::Gp2;
27// https://docs.outscale.com/api#createvolume
28const MAX_BSU_SIZE_GIB: usize = 14901;
29
30type DriveName = String;
31
32#[derive(Debug, Default)]
33pub struct Drives {
34    drives_cmd: HashMap<DriveName, Sender<DriveCmd>>,
35    drives_threads: ThreadPool,
36}
37
38impl Drives {
39    pub fn run(config: Config) -> Result<Drives, Box<dyn Error>> {
40        let mut drives_cmd = HashMap::<DriveName, Sender<DriveCmd>>::new();
41        let mut drive_list = Vec::<Drive>::new();
42
43        for drive_config in config.drives {
44            let name = drive_config.name.clone();
45
46            let (sender, receiver) = channel::<DriveCmd>();
47            let drive = Drive::new(drive_config, receiver);
48            drives_cmd.insert(name.clone(), sender);
49            drive_list.push(drive);
50        }
51
52        for (sender, drive) in Drives::discover_local_drives()? {
53            let name: String = drive.name.clone();
54            if drives_cmd.get(&name).is_some() {
55                continue;
56            }
57            drives_cmd.insert(name.clone(), sender);
58            drive_list.push(drive);
59        }
60
61        let drives_threads = ThreadPool::new(drive_list.len());
62        for mut drive in drive_list {
63            drives_threads.execute(move || drive.run());
64        }
65
66        Ok(Drives {
67            drives_cmd,
68            drives_threads,
69        })
70    }
71
72    pub fn stop(&mut self) -> Result<(), Box<dyn Error>> {
73        for (name, sender) in self.drives_cmd.iter() {
74            info!("asking drive {} to stop", name);
75            sender.send(DriveCmd::Stop)?;
76        }
77        info!("waiting for drives to stop");
78        self.drives_threads.join();
79        info!("all drives stopped");
80        Ok(())
81    }
82
83    pub fn discover_local_drives() -> Result<DriveDiscovery, Box<dyn Error>> {
84        // TODO
85        Ok(vec![])
86    }
87}
88
89type DriveDiscovery = Vec<(Sender<DriveCmd>, Drive)>;
90type DevicePath = String;
91
92#[derive(Debug)]
93pub enum DriveCmd {
94    Stop,
95}
96
97#[derive(Debug)]
98pub struct Drive {
99    last_reconcile: Instant,
100    all_bsu: Vec<Bsu>,
101    drive_cmd: Receiver<DriveCmd>,
102    exit: bool,
103    pv_to_be_initialized: Vec<DevicePath>,
104    pv_to_add_to_vg: Vec<DevicePath>,
105    pub name: String,
106    pub target: DriveTarget,
107    pub mount_path: String,
108    pub disk_type: config::DiskType,
109    pub disk_iops_per_gib: Option<usize>,
110    pub max_total_size_gib: Option<usize>,
111    pub initial_size_gib: usize,
112    pub max_bsu_count: usize,
113    pub max_used_space_perc: f32,
114    pub min_used_space_perc: f32,
115    pub disk_scale_factor_perc: f32,
116}
117
118impl Drive {
119    pub fn new(config: ConfigFileDrive, drive_cmd: Receiver<DriveCmd>) -> Self {
120        Drive {
121            last_reconcile: Instant::now() - Duration::of(RECONCILE_COOLDOWN_S as i64),
122            all_bsu: Vec::default(),
123            drive_cmd,
124            exit: false,
125            pv_to_be_initialized: Vec::new(),
126            pv_to_add_to_vg: Vec::new(),
127            name: config.name,
128            target: config.target,
129            mount_path: config.mount_path,
130            disk_type: config.disk_type.unwrap_or(DEFAULT_DISK_TYPE),
131            initial_size_gib: config.initial_size_gib.unwrap_or(DEFAULT_INITIAL_DISK_GIB),
132            max_bsu_count: config.max_bsu_count.unwrap_or(DEFAULT_MAX_DISKS),
133            max_used_space_perc: config.max_used_space_perc.unwrap_or(DEFAULT_MAX_USED_PERC) as f32
134                / 100.0,
135            min_used_space_perc: config.min_used_space_perc.unwrap_or(DEFAULT_MIN_USED_PERC) as f32
136                / 100.0,
137            disk_scale_factor_perc: config
138                .disk_scale_factor_perc
139                .unwrap_or(DEFAULT_SCALE_FACTOR_PERC) as f32
140                / 100.0,
141            disk_iops_per_gib: config.disk_iops_per_gib,
142            max_total_size_gib: config.max_total_size_gib,
143        }
144    }
145
146    pub fn run(&mut self) {
147        loop {
148            if Instant::now().seconds() - self.last_reconcile.seconds()
149                <= RECONCILE_COOLDOWN_S as i64
150            {
151                sleep(time::Duration::from_millis(10));
152                if self.early_exit().is_err() {
153                    break;
154                }
155                continue;
156            }
157            if let Err(err) = self.reconcile() {
158                error!("\"{}\" drive: {}", self.name, err);
159            } else {
160                info!("\"{}\" drive: reconcile loop over with success", self.name);
161            }
162            self.last_reconcile = Instant::now();
163            if self.exit {
164                break;
165            }
166        }
167        info!("\"{}\" drive: stopped", self.name);
168    }
169
170    pub fn early_exit(&mut self) -> Result<(), Box<dyn Error>> {
171        if let Ok(cmd) = self.drive_cmd.try_recv() {
172            info!("\"{}\" drive received {:?} command", self.name, cmd);
173            match cmd {
174                DriveCmd::Stop => {
175                    self.exit = true;
176                    return Err(Box::new(format_err!(
177                        "\"{}\" drive: early exit due to drive stop",
178                        self.name
179                    )));
180                }
181            };
182        }
183        Ok(())
184    }
185
186    pub fn reconcile(&mut self) -> Result<(), Box<dyn Error>> {
187        info!(
188            "\"{}\" drive: entering {:?} drive target",
189            self.name, self.target
190        );
191        info!(
192            "\"{}\" drive: start reconcile {}",
193            self.name,
194            self.target.to_string()
195        );
196        match self.target {
197            DriveTarget::Online => self.reconcile_online(),
198            DriveTarget::Offline => self.reconcile_offline(),
199            DriveTarget::Delete => self.reconcile_delete(),
200        }
201    }
202
203    pub fn reconcile_offline(&mut self) -> Result<(), Box<dyn Error>> {
204        self.early_exit()?;
205        while self.is_fs_mounted()? {
206            self.early_exit()?;
207            self.fs_umount()?;
208        }
209
210        self.disable_lv().ok();
211        self.disable_vg().ok();
212
213        self.early_exit()?;
214        self.fetch_all_drive_bsu()?;
215        if self.bsu_count() == 0 {
216            return Ok(());
217        }
218
219        while self.are_bsu_attached()? {
220            self.early_exit()?;
221            self.bsu_detach_all_from_this_vm()?;
222            self.early_exit()?;
223            self.fetch_all_drive_bsu()?;
224            self.early_exit()?;
225        }
226        self.vg_scan().ok();
227        Ok(())
228    }
229
230    pub fn reconcile_delete(&mut self) -> Result<(), Box<dyn Error>> {
231        self.reconcile_offline()?;
232        self.delete_all_bsu()?;
233        Ok(())
234    }
235
236    pub fn reconcile_online(&mut self) -> Result<(), Box<dyn Error>> {
237        'start_again: loop {
238            debug!("\"{}\" drive: reconcile online loop again", self.name);
239            self.early_exit()?;
240            self.crash_resume()?;
241
242            self.early_exit()?;
243            self.fetch_all_drive_bsu()?;
244
245            self.early_exit()?;
246            while !self.are_bsu_attached()? {
247                self.bsu_attach_missing()?;
248                self.fetch_all_drive_bsu()?;
249                self.early_exit()?;
250            }
251
252            self.early_exit()?;
253            if self.bsu_count() == 0 {
254                self.create_initial_bsu()?;
255                continue 'start_again;
256            }
257
258            self.early_exit()?;
259            while !self.are_pv_initialized()? {
260                self.pv_initialize_missing()?;
261                self.early_exit()?;
262            }
263
264            self.early_exit()?;
265            self.vg_scan().ok();
266
267            self.early_exit()?;
268            while !self.is_vg_created()? {
269                self.vg_create()?;
270                self.early_exit()?;
271            }
272
273            self.early_exit()?;
274            self.enable_vg().ok();
275
276            self.early_exit()?;
277            while !self.is_vg_extended()? {
278                self.vg_extend()?;
279                self.early_exit()?;
280            }
281
282            self.early_exit()?;
283            while !self.is_lv_created()? {
284                self.lv_create()?;
285                self.early_exit()?;
286            }
287
288            self.early_exit()?;
289            self.enable_lv().ok();
290
291            self.early_exit()?;
292            self.lv_extend()?;
293
294            self.early_exit()?;
295            while !self.is_fs_formated()? {
296                self.fs_format()?;
297                self.early_exit()?;
298            }
299
300            self.early_exit()?;
301            while !self.is_mount_path_created() {
302                self.create_mount_path()?;
303            }
304
305            self.early_exit()?;
306            while !self.is_fs_mounted()? {
307                self.fs_mount()?;
308                self.early_exit()?;
309            }
310
311            self.early_exit()?;
312            while !self.is_fs_extended()? {
313                self.fs_extend()?;
314                self.early_exit()?;
315            }
316
317            self.early_exit()?;
318            if self.is_drive_reached_max_attached_bsu()? {
319                self.remove_smallest_bsu()?;
320                self.early_exit()?;
321                continue 'start_again;
322            }
323
324            self.early_exit()?;
325            if self.is_drive_low_space_left()? {
326                if self.is_max_space_reached() {
327                    return Ok(());
328                }
329                if !self.is_drive_reached_max_attached_bsu_minus_one()?
330                    && !self.is_drive_contains_smallest_bsu()
331                {
332                    self.create_smaller_bsu()?;
333                } else {
334                    self.create_larger_bsu()?;
335                }
336                continue 'start_again;
337            }
338
339            self.early_exit()?;
340            if self.is_drive_high_space_left()? {
341                if self.bsu_count() > 1 {
342                    self.remove_largest_bsu()?;
343                } else {
344                    if self.has_minimal_size() {
345                        return Ok(());
346                    }
347                    self.create_ideal_bsu()?;
348                }
349                self.early_exit()?;
350                continue 'start_again;
351            }
352            return Ok(());
353        }
354    }
355
356    pub fn crash_resume(&mut self) -> Result<(), Box<dyn Error>> {
357        // Run pvmove alone to restart eventual pvmove actions
358        // https://www.man7.org/linux/man-pages/man8/pvmove.8.html
359        lvm::pv_move_no_arg()?;
360        Ok(())
361    }
362
363    pub fn fetch_all_drive_bsu(&mut self) -> Result<(), Box<dyn Error>> {
364        debug!("\"{}\" drive: fetch all bsu", self.name);
365        self.all_bsu = Bsu::fetch_drive(&self.name)?;
366        info!(
367            "\"{}\" drive: fetched {} BSU",
368            self.name,
369            self.all_bsu.len()
370        );
371        Ok(())
372    }
373
374    pub fn are_bsu_attached(&mut self) -> Result<bool, Box<dyn Error>> {
375        let mut ret = true;
376        debug!("\"{}\" drive: are bsu attached ?", self.name);
377        let vm_id = VM_ID.try_read()?;
378        for bsu in self.all_bsu.iter() {
379            let Some(bsu_vm_id) = &bsu.vm_id else {
380                debug!(
381                    "\"{}\" drive: BSU id {} not attached to any VM",
382                    self.name, bsu.id
383                );
384                ret = false;
385                continue;
386            };
387            if *bsu_vm_id != *vm_id {
388                debug!(
389                    "\"{}\" drive: BSU id {} is attached to vm {} instead of vm {}",
390                    self.name, bsu.id, bsu_vm_id, vm_id
391                );
392                ret = false;
393                continue;
394            }
395            let Some(device_name) = &bsu.device_path else {
396                debug!(
397                    "\"{}\" drive: BSU id {} does not have a device path",
398                    self.name, bsu.id
399                );
400                ret = false;
401                continue;
402            };
403            let device_path = Path::new(device_name);
404            if !device_path.exists() {
405                debug!(
406                    "\"{}\" drive: BSU id {} seems not to exist yet on {}",
407                    self.name, bsu.id, device_name
408                );
409                ret = false;
410                continue;
411            }
412            debug!(
413                "\"{}\" drive: bsu id {} of size {}B ({}GiB) is attached",
414                self.name,
415                bsu.id,
416                bsu.size_bytes,
417                bytes_to_gib(bsu.size_bytes)
418            );
419        }
420        info!("\"{}\" drive: are bsu attached ? -> {}", self.name, ret);
421        Ok(ret)
422    }
423
424    pub fn bsu_attach_missing(&mut self) -> Result<(), Box<dyn Error>> {
425        let vm_id: String = VM_ID.try_read()?.clone();
426        let bsus: Vec<Bsu> = self
427            .all_bsu
428            .iter()
429            .filter(|bsu| bsu.vm_id.is_none())
430            .cloned()
431            .collect();
432        Bsu::multiple_attach(&vm_id, &bsus)
433    }
434
435    pub fn bsu_detach_all_from_this_vm(&mut self) -> Result<(), Box<dyn Error>> {
436        info!(
437            "\"{}\" drive: detach all {} BSU",
438            self.name,
439            self.all_bsu.len()
440        );
441        Bsu::multiple_detach(&self.all_bsu)
442    }
443
444    pub fn delete_all_bsu(&mut self) -> Result<(), Box<dyn Error>> {
445        info!(
446            "\"{}\" drive: delete all {} BSU",
447            self.name,
448            self.all_bsu.len()
449        );
450        for bsu in self.all_bsu.iter() {
451            bsu.delete()?;
452        }
453        Ok(())
454    }
455
456    pub fn bsu_count(&mut self) -> usize {
457        let count = self.all_bsu.len();
458        debug!("\"{}\" drive: bsu count = {}", self.name, count);
459        count
460    }
461
462    pub fn create_initial_bsu(&mut self) -> Result<(), Box<dyn Error>> {
463        debug!("\"{}\" drive: create initial BSU", self.name);
464        Bsu::create_gib(
465            &self.name,
466            &self.disk_type,
467            self.disk_iops_per_gib,
468            self.initial_size_gib,
469        )
470    }
471
472    pub fn are_pv_initialized(&mut self) -> Result<bool, Box<dyn Error>> {
473        let mut ret = true;
474        self.pv_to_be_initialized.clear();
475        let mut found_devices = HashSet::<String>::new();
476        if let Some(report_with_no_vg) = lvm::get_report_with_no_vg()? {
477            for device in report_with_no_vg.devices() {
478                found_devices.insert(device);
479            }
480        }
481        if let Some(report) = lvm::get_report(&self.name)? {
482            for device in report.devices() {
483                found_devices.insert(device);
484            }
485        }
486        for bsu in self.all_bsu.iter() {
487            let Some(device_path) = &bsu.device_path else {
488                error!(
489                    "\"{}\" drive: BSU {} should have loca path, please report error",
490                    self.name, bsu.id
491                );
492                continue;
493            };
494            if !found_devices.contains(device_path) {
495                info!(
496                    "\"{}\" drive: BSU {} ({}) seems not to be pv initialized",
497                    self.name, bsu.id, device_path
498                );
499                self.pv_to_be_initialized.push(device_path.clone());
500                ret = false;
501            }
502        }
503        info!("\"{}\" drive: are pv initialized -> {}", self.name, ret);
504        Ok(ret)
505    }
506
507    pub fn pv_initialize_missing(&mut self) -> Result<(), Box<dyn Error>> {
508        for device in self.pv_to_be_initialized.iter() {
509            lvm::init_pv(device)?;
510        }
511        Ok(())
512    }
513
514    pub fn is_vg_created(&mut self) -> Result<bool, Box<dyn Error>> {
515        let lvm = lvm::get_report(&self.name)?;
516        info!(
517            "\"{}\" drive: is vg created -> {}",
518            self.name,
519            lvm.is_some()
520        );
521        Ok(lvm.is_some())
522    }
523
524    pub fn vg_create(&mut self) -> Result<(), Box<dyn Error>> {
525        debug!("\"{}\" drive: create vg", self.name);
526        let mut found_devices = HashSet::<String>::new();
527        if let Some(report_with_no_vg) = lvm::get_report_with_no_vg()? {
528            for device in report_with_no_vg.devices() {
529                found_devices.insert(device);
530            }
531        }
532        for bsu in self.all_bsu.iter() {
533            let Some(device_path) = &bsu.device_path else {
534                error!(
535                    "\"{}\" drive: BSU {} should have local path, please report error",
536                    self.name, bsu.id
537                );
538                continue;
539            };
540            if found_devices.contains(device_path) {
541                return lvm::vg_create(&self.name, device_path);
542            }
543        }
544        Err(Box::new(format_err!(
545            "\"{}\" drive: no PV found to init VG, please report this error",
546            self.name
547        )))
548    }
549
550    pub fn is_vg_extended(&mut self) -> Result<bool, Box<dyn Error>> {
551        let mut ret = true;
552        self.pv_to_add_to_vg.clear();
553        let mut found_devices = HashSet::<String>::new();
554        if let Some(report_with_no_vg) = lvm::get_report_with_no_vg()? {
555            for device in report_with_no_vg.devices() {
556                found_devices.insert(device);
557            }
558        }
559        for bsu in self.all_bsu.iter() {
560            let Some(device_path) = &bsu.device_path else {
561                error!(
562                    "\"{}\" drive: BSU {} should have local path, please report error",
563                    self.name, bsu.id
564                );
565                continue;
566            };
567            if found_devices.contains(device_path) {
568                info!(
569                    "\"{}\" drive: pv {} can be added to vg",
570                    self.name, device_path
571                );
572                self.pv_to_add_to_vg.push(device_path.clone());
573                ret = false;
574            }
575        }
576        info!("\"{}\" drive: is vg extended -> {}", self.name, ret);
577        Ok(ret)
578    }
579
580    pub fn vg_extend(&mut self) -> Result<(), Box<dyn Error>> {
581        for pv_device_path in self.pv_to_add_to_vg.iter() {
582            lvm::extend_vg(&self.name, pv_device_path)?;
583        }
584        Ok(())
585    }
586
587    pub fn is_lv_created(&mut self) -> Result<bool, Box<dyn Error>> {
588        let Some(lvm) = lvm::get_report(&self.name)? else {
589            return Err(Box::new(format_err!(
590                "\"{}\" drive: lvm details cannot be found, please report issue",
591                self.name
592            )));
593        };
594        let Some(_lv) = lvm.lv.into_iter().next() else {
595            debug!("\"{}\" drive: is lv created -> false", self.name);
596            return Ok(false);
597        };
598        info!("\"{}\" drive: is lv created -> true", self.name);
599        Ok(true)
600    }
601
602    pub fn lv_create(&mut self) -> Result<(), Box<dyn Error>> {
603        lvm::create_lv(&self.name)
604    }
605
606    pub fn lv_extend(&mut self) -> Result<(), Box<dyn Error>> {
607        let vg_size = lvm::get_vg_size_bytes(&self.name)?;
608        let lv_size = lvm::get_lv_size_bytes(&self.name)?;
609        match vg_size.cmp(&lv_size) {
610            Ordering::Greater => {
611                debug!("\"{}\" drive: lv can be extended", self.name);
612                let lv_path = lvm::lv_path(&self.name);
613                lvm::lv_extend_full(&lv_path)?;
614            }
615            Ordering::Equal => debug!("\"{}\" drive: lv fit vg", self.name),
616            Ordering::Less => {
617                return Err(Box::new(format_err!(
618                    "\"{}\" drive: vg_size ({}) < lv_size ({})",
619                    self.name,
620                    vg_size,
621                    lv_size
622                )));
623            }
624        };
625        Ok(())
626    }
627
628    pub fn enable_lv(&mut self) -> Result<(), Box<dyn Error>> {
629        debug!("\"{}\" drive: disabling lv {}", self.name, self.name);
630        lvm::lv_activate(true, &self.name)
631    }
632
633    pub fn disable_lv(&mut self) -> Result<(), Box<dyn Error>> {
634        debug!("\"{}\" drive: disabling lv {}", self.name, self.name);
635        lvm::lv_activate(false, &self.name)
636    }
637
638    pub fn enable_vg(&mut self) -> Result<(), Box<dyn Error>> {
639        debug!("\"{}\" drive: enabling vg {}", self.name, self.name);
640        lvm::vg_activate(true, &self.name)
641    }
642
643    pub fn disable_vg(&mut self) -> Result<(), Box<dyn Error>> {
644        debug!("\"{}\" drive: disabling vg {}", self.name, self.name);
645        lvm::vg_activate(false, &self.name)
646    }
647
648    pub fn vg_scan(&self) -> Result<(), Box<dyn Error>> {
649        debug!("\"{}\" drive: vgscan", self.name);
650        lvm::vg_scan()
651    }
652
653    pub fn is_fs_formated(&mut self) -> Result<bool, Box<dyn Error>> {
654        let lv_path = lvm::lv_path(&self.name);
655        let ret = fs::device_seems_formated(&lv_path)?;
656        info!("\"{}\" drive: is fs formated -> {}", self.name, ret);
657        Ok(ret)
658    }
659
660    pub fn fs_format(&mut self) -> Result<(), Box<dyn Error>> {
661        debug!("\"{}\" drive: fs format", self.name);
662        let lv_path = lvm::lv_path(&self.name);
663        fs::format(&lv_path)
664    }
665
666    pub fn is_mount_path_created(&mut self) -> bool {
667        let ret = fs::is_folder(&self.mount_path);
668        debug!(
669            "\"{}\" drive: is mount target created ? -> {}",
670            self.name, ret
671        );
672        ret
673    }
674
675    pub fn create_mount_path(&mut self) -> Result<(), Box<dyn Error>> {
676        debug!(
677            "\"{}\" drive: try creating folder in {}",
678            self.name, self.mount_path
679        );
680        fs::create_folder(&self.mount_path)
681    }
682
683    pub fn is_fs_mounted(&mut self) -> Result<bool, Box<dyn Error>> {
684        let lv_path = lvm::lv_path(&self.name);
685        let ret = fs::is_mounted(&lv_path, &self.mount_path)?;
686        info!("\"{}\" drive: is fs mounted ? -> {}", self.name, ret);
687        Ok(ret)
688    }
689
690    pub fn fs_mount(&mut self) -> Result<(), Box<dyn Error>> {
691        debug!("\"{}\" drive: fs mount", self.name);
692        let lv_path = lvm::lv_path(&self.name);
693        fs::mount(&lv_path, &self.mount_path)
694    }
695
696    pub fn fs_umount(&mut self) -> Result<(), Box<dyn Error>> {
697        debug!("\"{}\" drive: fs umount", self.name);
698        let lv_path = lvm::lv_path(&self.name);
699        fs::umount(&lv_path)
700    }
701
702    pub fn is_fs_extended(&mut self) -> Result<bool, Box<dyn Error>> {
703        let lv_size = lvm::get_lv_size_bytes(&self.name)?;
704        let lv_path = lvm::lv_path(&self.name);
705        let fs_size = fs::size_bytes(&lv_path)?;
706        debug!(
707            "\"{}\" drive: lv size: {}B ({}GiB), fs size: {}B ({}GiB)",
708            self.name,
709            lv_size,
710            bytes_to_gib(lv_size),
711            fs_size,
712            bytes_to_gib(fs_size)
713        );
714        let ret = match fs_size.cmp(&lv_size) {
715            Ordering::Equal => true,
716            Ordering::Less => false,
717            Ordering::Greater => {
718                return Err(Box::new(format_err!(
719                    "\"{}\" drive: fs_size > lv_size",
720                    self.name
721                )))
722            }
723        };
724        info!("\"{}\" drive: is fs extended ? -> {}", self.name, ret);
725        Ok(ret)
726    }
727
728    pub fn fs_extend(&mut self) -> Result<(), Box<dyn Error>> {
729        debug!("\"{}\" drive: fs extend", self.name);
730        fs::extend_fs_max(&self.mount_path)
731    }
732
733    pub fn is_drive_reached_max_attached_bsu(&mut self) -> Result<bool, Box<dyn Error>> {
734        let count = self.bsu_count();
735        let ret = count >= self.max_bsu_count;
736        info!(
737            "\"{}\" drive: is drive reached max attached BSU: (count: {}, max: {}) -> {}",
738            self.name, count, self.max_bsu_count, ret
739        );
740        Ok(ret)
741    }
742
743    pub fn is_drive_reached_max_attached_bsu_minus_one(&mut self) -> Result<bool, Box<dyn Error>> {
744        let ret = self.bsu_count() == self.max_bsu_count - 1;
745        info!(
746            "\"{}\" drive: is drive reached max attached BSU minus ONE (count: {}, max: {}) -> {}",
747            self.name,
748            self.all_bsu.len(),
749            self.max_bsu_count,
750            ret
751        );
752        Ok(ret)
753    }
754
755    pub fn is_drive_contains_smallest_bsu(&mut self) -> bool {
756        let ret = self.smallest_bsu().size_gib <= self.initial_size_gib;
757        debug!(
758            "\"{}\" drive: is_drive_contains_smallest_bsu ? -> {}",
759            self.name, ret
760        );
761        ret
762    }
763
764    pub fn remove_smallest_bsu(&mut self) -> Result<(), Box<dyn Error>> {
765        debug!("\"{}\" drive: remove smallest BSU", self.name);
766        let bsu = self.smallest_bsu();
767        self.remove_bsu(&bsu)
768    }
769
770    pub fn is_drive_low_space_left(&mut self) -> Result<bool, Box<dyn Error>> {
771        let lv_path = lvm::lv_path(&self.name);
772        let usage_per = fs::used_perc(&lv_path)?;
773        let ret = usage_per >= self.max_used_space_perc;
774        debug!(
775            "\"{}\" drive: used space perc: {}, max_used_space_perc: {}",
776            self.name, usage_per, self.max_used_space_perc
777        );
778        info!(
779            "\"{}\" drive: is drive low space left -> {}",
780            self.name, ret
781        );
782        Ok(ret)
783    }
784
785    pub fn is_max_space_reached(&mut self) -> bool {
786        let Some(max_total_size_gib) = self.max_total_size_gib else {
787            return false;
788        };
789        let total_gib = self.all_bsu_size_gib();
790        let ret = total_gib >= max_total_size_gib;
791        info!(
792            "\"{}\" drive: is max space reached -> {} ({}/{}Gib)",
793            self.name, ret, total_gib, max_total_size_gib
794        );
795        ret
796    }
797
798    pub fn all_bsu_size_gib(&self) -> usize {
799        let mut total_size: usize = 0;
800        for bsu in self.all_bsu.iter() {
801            total_size += bsu.size_bytes;
802        }
803        bytes_to_gib_rounded(total_size)
804    }
805
806    pub fn create_larger_bsu(&mut self) -> Result<(), Box<dyn Error>> {
807        debug!("\"{}\" drive: create larger BSU", self.name);
808        let largest_size_gib = self.largest_bsu().size_gib as f32;
809        let new_bsu_size_gib =
810            (largest_size_gib + largest_size_gib * self.disk_scale_factor_perc).ceil() as usize;
811        let final_bsu_size = min(MAX_BSU_SIZE_GIB, new_bsu_size_gib);
812        Bsu::create_gib(
813            &self.name,
814            &self.disk_type,
815            self.disk_iops_per_gib,
816            final_bsu_size,
817        )
818    }
819
820    pub fn create_smaller_bsu(&mut self) -> Result<(), Box<dyn Error>> {
821        debug!("\"{}\" drive: create smaller BSU", self.name);
822        let largest_size_gib = self.smallest_bsu().size_gib as f32;
823        let new_bsu_size_gib =
824            (largest_size_gib - largest_size_gib * self.disk_scale_factor_perc).ceil() as usize;
825        let final_bsu_size = max(self.initial_size_gib, new_bsu_size_gib);
826        Bsu::create_gib(
827            &self.name,
828            &self.disk_type,
829            self.disk_iops_per_gib,
830            final_bsu_size,
831        )
832    }
833
834    pub fn largest_bsu(&self) -> Bsu {
835        let mut largest_bsu_size = 0;
836        let mut largest_bsu: Option<&Bsu> = None;
837        for bsu in self.all_bsu.iter() {
838            if bsu.size_bytes > largest_bsu_size {
839                largest_bsu = Some(bsu);
840                largest_bsu_size = bsu.size_bytes;
841            }
842        }
843        largest_bsu
844            .expect("largest BSU should exit, please report this")
845            .clone()
846    }
847
848    pub fn smallest_bsu(&self) -> Bsu {
849        let mut smallest_bsu_size = usize::MAX;
850        let mut smallest_bsu: Option<&Bsu> = None;
851        for bsu in self.all_bsu.iter() {
852            if bsu.size_bytes < smallest_bsu_size {
853                smallest_bsu = Some(bsu);
854                smallest_bsu_size = bsu.size_bytes;
855            }
856        }
857        smallest_bsu
858            .expect("smallest BSU should exit, please report this")
859            .clone()
860    }
861
862    pub fn is_drive_high_space_left(&mut self) -> Result<bool, Box<dyn Error>> {
863        let lv_path = lvm::lv_path(&self.name);
864        let usage_per = fs::used_perc(&lv_path)?;
865        let ret = usage_per <= self.min_used_space_perc;
866        debug!(
867            "\"{}\" drive: used space perc: {}, low space perc: {}",
868            self.name, usage_per, self.min_used_space_perc
869        );
870        info!(
871            "\"{}\" drive: is drive high space left -> {}",
872            self.name, ret
873        );
874        Ok(ret)
875    }
876
877    pub fn has_minimal_size(&self) -> bool {
878        let total_size_gib = self.all_bsu_size_gib();
879        let ret = total_size_gib == self.initial_size_gib;
880        info!("\"{}\" drive: has minimal size -> {}", self.name, ret);
881        ret
882    }
883
884    pub fn ideal_size_bytes(&mut self) -> Result<usize, Box<dyn Error>> {
885        let lv_path = lvm::lv_path(&self.name);
886        let used_size_bytes = fs::used_bytes(&lv_path)? as f32;
887        let middle_perc = (self.min_used_space_perc + self.max_used_space_perc) / 2.0;
888        let ideal_size_bytes = (used_size_bytes / middle_perc).ceil() as usize;
889        let ideal_size_bytes = max(ideal_size_bytes, gib_to_bytes(self.initial_size_gib));
890        let ideal_size_bytes = min(ideal_size_bytes, fs::size_bytes(&lv_path)?);
891        Ok(ideal_size_bytes)
892    }
893
894    pub fn create_ideal_bsu(&mut self) -> Result<(), Box<dyn Error>> {
895        let ideal_size_gib = bytes_to_gib_rounded(self.ideal_size_bytes()?);
896        info!(
897            "\"{}\" drive: create fit BSU of size {}GiB",
898            self.name, ideal_size_gib
899        );
900        Bsu::create_gib(
901            &self.name,
902            &self.disk_type,
903            self.disk_iops_per_gib,
904            ideal_size_gib,
905        )?;
906        Ok(())
907    }
908
909    pub fn remove_largest_bsu(&mut self) -> Result<(), Box<dyn Error>> {
910        info!("\"{}\" drive: remove largest BSU", self.name);
911        let bsu = self.largest_bsu();
912        self.remove_bsu(&bsu)
913    }
914
915    pub fn remove_bsu(&mut self, bsu: &Bsu) -> Result<(), Box<dyn Error>> {
916        info!(
917            "removing BSU {} of size {}B ({}GiB)",
918            bsu.id,
919            bsu.size_bytes,
920            bytes_to_gib(bsu.size_bytes)
921        );
922        let lv_path = lvm::lv_path(&self.name);
923        let free_space_bytes = fs::available_bytes(&lv_path)?;
924        if free_space_bytes < bsu.size_bytes {
925            return Err(Box::new(format_err!(
926                "\"{}\" drive: cannot remove BSU. free space left: {}B ({}GiB), bsu size to remove: {} ({}GiB)",
927                self.name,
928                free_space_bytes,
929                bytes_to_gib(free_space_bytes),
930                bsu.size_bytes,
931                bytes_to_gib(bsu.size_bytes)
932            )));
933        }
934        let Some(device_path) = &bsu.device_path else {
935            return Err(Box::new(format_err!(
936                "\"{}\" drive: cannot find device path for BSU {}",
937                self.name,
938                bsu.id
939            )));
940        };
941
942        let ideal_size_bytes = self.ideal_size_bytes()?;
943        let fs_size_bytes = fs::size_bytes(&lv_path)?;
944        let largest_possible_new_fs_size = fs_size_bytes - bsu.size_bytes;
945        // trying (when possible) to lower more than required to delete the BSU will drastically help pvmove not to move useless fs data.
946        let new_fs_size_bytes = min(largest_possible_new_fs_size, ideal_size_bytes);
947
948        debug!(
949            "\"{}\" drive: resising fs & lv to {}B ({}GiB)",
950            self.name,
951            new_fs_size_bytes,
952            bytes_to_gib(new_fs_size_bytes)
953        );
954        debug!(
955            "\"{}\" drive: ideal_size_bytes was {}B ({}GiB)",
956            self.name,
957            ideal_size_bytes,
958            bytes_to_gib(ideal_size_bytes)
959        );
960        debug!(
961            "\"{}\" drive: largest_possible_new_fs_size was {}B ({}GiB)",
962            self.name,
963            largest_possible_new_fs_size,
964            bytes_to_gib(largest_possible_new_fs_size)
965        );
966
967        fs::resize(&self.mount_path, new_fs_size_bytes)?;
968        let lv_path = lvm::lv_path(&self.name);
969        lvm::lv_reduce(&lv_path, new_fs_size_bytes)?;
970        lvm::pv_move(device_path)?;
971        lvm::vg_reduce(&self.name, device_path)?;
972        lvm::pv_remove(device_path)?;
973        // Once pv moved, be sure we can expand back lv and fs.
974        self.lv_extend()?;
975        self.fs_extend()?;
976
977        bsu.detach()?;
978        bsu.delete()?;
979        Ok(())
980    }
981}