use crate::common::{
BackingFile, LoopbackDevice, Mount, single_mount, write_test_data,
};
use btrfs_uapi::{
balance::{
BalanceArgs, BalanceCtl, BalanceFlags, BalanceState, balance,
balance_ctl, balance_progress,
},
chunk::device_chunk_allocations,
device::device_add,
filesystem::sync,
space::{BlockGroupFlags, space_info},
};
use nix::errno::Errno;
use std::{ffi::CString, fs::File, os::unix::io::AsFd};
#[test]
#[ignore = "requires elevated privileges"]
fn balance_progress_idle() {
let (_td, mnt) = single_mount();
match balance_progress(mnt.fd()) {
Err(e) if e == Errno::ENOTCONN => {
}
Err(e) => panic!("unexpected error from balance_progress: {e}"),
Ok((state, progress)) => {
assert!(
!state.contains(BalanceState::RUNNING),
"expected no running balance, got state={state:?} progress={progress:?}"
);
}
}
}
#[test]
#[ignore = "requires elevated privileges"]
fn balance_pause_not_running() {
let (_td, mnt) = single_mount();
match balance_ctl(mnt.fd(), BalanceCtl::Pause) {
Err(e) if e == Errno::ENOTCONN => { }
Err(e) => panic!("unexpected error from balance_ctl(Pause): {e}"),
Ok(()) => panic!("expected ENOTCONN, but pause succeeded"),
}
}
#[test]
#[ignore = "requires elevated privileges"]
fn balance_cancel_not_running() {
let (_td, mnt) = single_mount();
match balance_ctl(mnt.fd(), BalanceCtl::Cancel) {
Err(e) if e == Errno::ENOTCONN => { }
Err(e) => panic!("unexpected error from balance_ctl(Cancel): {e}"),
Ok(()) => panic!("expected ENOTCONN, but cancel succeeded"),
}
}
#[test]
#[ignore = "requires elevated privileges"]
fn balance_full_completes() {
let (_td, mnt) = single_mount();
let flags =
BalanceFlags::DATA | BalanceFlags::METADATA | BalanceFlags::SYSTEM;
let progress =
balance(mnt.fd(), flags, None, None, None).expect("balance failed");
assert_eq!(
progress.completed, progress.considered,
"expected all considered chunks to be completed: {progress:?}"
);
}
#[test]
#[ignore = "requires elevated privileges"]
fn balance_cancel_in_flight() {
let (_td, mnt) = single_mount();
let mount_path = mnt.path().to_path_buf();
let balance_thread = std::thread::spawn(move || {
let file =
File::open(&mount_path).expect("failed to open mount in thread");
let flags =
BalanceFlags::DATA | BalanceFlags::METADATA | BalanceFlags::SYSTEM;
balance(file.as_fd(), flags, None, None, None)
});
std::thread::sleep(std::time::Duration::from_millis(200));
match balance_ctl(mnt.fd(), BalanceCtl::Cancel) {
Ok(()) | Err(Errno::ENOTCONN) => {}
Err(e) => panic!("unexpected error from balance_ctl(Cancel): {e}"),
}
let result = balance_thread.join().expect("balance thread panicked");
match result {
Ok(_) | Err(Errno::ECANCELED) => {}
Err(e) => panic!("balance returned unexpected error: {e}"),
}
}
#[test]
#[ignore = "requires elevated privileges"]
fn balance_convert_raid1() {
let td = tempfile::tempdir().unwrap();
let f1 = BackingFile::new(td.path(), "d1.img", 300_000_000);
f1.mkfs();
let lo1 = LoopbackDevice::new(f1);
let mnt = Mount::new(lo1, td.path());
write_test_data(mnt.path(), "data.bin", 50_000_000);
sync(mnt.fd()).unwrap();
let f2 = BackingFile::new(td.path(), "d2.img", 300_000_000);
let lo2 = LoopbackDevice::new(f2);
let dev2_cpath = CString::new(lo2.path().to_str().unwrap()).unwrap();
device_add(mnt.fd(), &dev2_cpath).expect("device_add failed");
let convert_args =
BalanceArgs::new().convert(BlockGroupFlags::RAID1.bits());
let flags =
BalanceFlags::DATA | BalanceFlags::METADATA | BalanceFlags::SYSTEM;
balance(
mnt.fd(),
flags,
Some(convert_args.clone()),
Some(convert_args.clone()),
Some(convert_args),
)
.expect("balance failed");
let spaces = space_info(mnt.fd()).expect("space_info failed");
let data_entry = spaces
.iter()
.find(|s| s.flags.contains(BlockGroupFlags::DATA))
.expect("no data entry in space_info");
assert!(
data_entry.flags.contains(BlockGroupFlags::RAID1),
"data should be RAID1 after conversion, got {data_entry:?}",
);
let allocs = device_chunk_allocations(mnt.fd())
.expect("device_chunk_allocations failed");
let dev1_bytes: u64 = allocs
.iter()
.filter(|a| a.devid == 1)
.map(|a| a.bytes)
.sum();
let dev2_bytes: u64 = allocs
.iter()
.filter(|a| a.devid == 2)
.map(|a| a.bytes)
.sum();
assert!(dev1_bytes > 0, "device 1 should have allocations");
assert!(dev2_bytes > 0, "device 2 should have allocations");
}
#[test]
#[ignore = "requires elevated privileges"]
fn balance_pause_resume() {
let td = tempfile::tempdir().unwrap();
let f = BackingFile::new(td.path(), "disk.img", 512_000_000);
f.mkfs();
let lo = LoopbackDevice::new(f);
let mnt = Mount::new(lo, td.path());
write_test_data(mnt.path(), "data.bin", 200_000_000);
sync(mnt.fd()).unwrap();
let mount_path = mnt.path().to_path_buf();
let balance_thread = std::thread::spawn(move || {
let file =
File::open(&mount_path).expect("failed to open mount in thread");
let flags =
BalanceFlags::DATA | BalanceFlags::METADATA | BalanceFlags::SYSTEM;
balance(file.as_fd(), flags, None, None, None)
});
std::thread::sleep(std::time::Duration::from_millis(100));
match balance_ctl(mnt.fd(), BalanceCtl::Pause) {
Ok(()) => {
match balance_progress(mnt.fd()) {
Ok((state, _)) => {
assert!(
state.contains(BalanceState::PAUSE_REQ)
|| !state.contains(BalanceState::RUNNING),
"expected paused state, got {state:?}",
);
}
Err(Errno::ENOTCONN) => {
}
Err(e) => panic!("unexpected error from balance_progress: {e}"),
}
let resume_fd =
File::open(&mnt.path()).expect("failed to open mount");
let flags = BalanceFlags::DATA
| BalanceFlags::METADATA
| BalanceFlags::SYSTEM
| BalanceFlags::RESUME;
match balance(resume_fd.as_fd(), flags, None, None, None) {
Ok(_) | Err(Errno::ENOTCONN) | Err(Errno::ECANCELED) => {}
Err(e) => panic!("unexpected error resuming balance: {e}"),
}
}
Err(Errno::ENOTCONN) => {
}
Err(e) => panic!("unexpected error from balance_ctl(Pause): {e}"),
}
let result = balance_thread.join().expect("balance thread panicked");
match result {
Ok(_) | Err(Errno::ECANCELED) => {}
Err(e) => panic!("balance returned unexpected error: {e}"),
}
}
#[test]
#[ignore = "requires elevated privileges"]
fn balance_cancel_with_data() {
let td = tempfile::tempdir().unwrap();
let f = BackingFile::new(td.path(), "disk.img", 512_000_000);
f.mkfs();
let lo = LoopbackDevice::new(f);
let mnt = Mount::new(lo, td.path());
write_test_data(mnt.path(), "data.bin", 200_000_000);
sync(mnt.fd()).unwrap();
let mount_path = mnt.path().to_path_buf();
let balance_thread = std::thread::spawn(move || {
let file =
File::open(&mount_path).expect("failed to open mount in thread");
let flags =
BalanceFlags::DATA | BalanceFlags::METADATA | BalanceFlags::SYSTEM;
balance(file.as_fd(), flags, None, None, None)
});
std::thread::sleep(std::time::Duration::from_millis(100));
match balance_ctl(mnt.fd(), BalanceCtl::Cancel) {
Ok(()) | Err(Errno::ENOTCONN) => {}
Err(e) => panic!("unexpected error from balance_ctl(Cancel): {e}"),
}
let result = balance_thread.join().expect("balance thread panicked");
match result {
Ok(_) | Err(Errno::ECANCELED) => {}
Err(e) => panic!("balance returned unexpected error: {e}"),
}
}