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;
27const 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 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 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 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 self.lv_extend()?;
975 self.fs_extend()?;
976
977 bsu.detach()?;
978 bsu.delete()?;
979 Ok(())
980 }
981}