use alloc::collections::BTreeMap;
use alloc::vec;
use alloc::vec::Vec;
use crate::FsError;
use crate::integrity::checksum::Checksum;
#[derive(Debug, Clone)]
pub struct MirrorConfig {
pub copies: usize,
pub block_size: usize,
pub load_balance_reads: bool,
pub auto_heal: bool,
}
impl Default for MirrorConfig {
fn default() -> Self {
Self {
copies: 2,
block_size: 4096,
load_balance_reads: true,
auto_heal: true,
}
}
}
impl MirrorConfig {
pub fn two_way() -> Self {
Self {
copies: 2,
..Default::default()
}
}
pub fn three_way() -> Self {
Self {
copies: 3,
..Default::default()
}
}
pub fn with_block_size(mut self, size: usize) -> Self {
self.block_size = size;
self
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ChildState {
Online,
Degraded,
Faulted,
Resilvering,
Offline,
}
#[derive(Debug, Clone)]
pub struct MirrorChild {
pub device_id: usize,
pub state: ChildState,
pub read_errors: u64,
pub write_errors: u64,
pub checksum_errors: u64,
pub bytes_read: u64,
pub bytes_written: u64,
}
impl MirrorChild {
pub fn new(device_id: usize) -> Self {
Self {
device_id,
state: ChildState::Online,
read_errors: 0,
write_errors: 0,
checksum_errors: 0,
bytes_read: 0,
bytes_written: 0,
}
}
pub fn is_readable(&self) -> bool {
matches!(self.state, ChildState::Online | ChildState::Degraded)
}
pub fn is_writable(&self) -> bool {
matches!(
self.state,
ChildState::Online | ChildState::Degraded | ChildState::Resilvering
)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MirrorError {
AllChildrenFailed,
InsufficientChildren,
AllCopiesCorrupt,
DeviceNotFound(usize),
ResilverInProgress,
NoHealthySource,
IoError(&'static str),
InvalidConfig(&'static str),
}
impl From<MirrorError> for FsError {
fn from(e: MirrorError) -> Self {
match e {
MirrorError::AllChildrenFailed => FsError::IoError {
vdev: 0,
reason: "all mirror children failed",
},
MirrorError::InsufficientChildren => FsError::InvalidArgument {
reason: "insufficient children for mirror",
},
MirrorError::AllCopiesCorrupt => FsError::ChecksumMismatch {
expected: [0; 4],
actual: [0; 4],
},
MirrorError::DeviceNotFound(id) => FsError::IoError {
vdev: id,
reason: "device not found",
},
MirrorError::ResilverInProgress => FsError::IoError {
vdev: 0,
reason: "resilver already in progress",
},
MirrorError::NoHealthySource => FsError::IoError {
vdev: 0,
reason: "no healthy source for resilver",
},
MirrorError::IoError(msg) => FsError::IoError {
vdev: 0,
reason: msg,
},
MirrorError::InvalidConfig(msg) => FsError::InvalidArgument { reason: msg },
}
}
}
#[derive(Debug, Clone)]
pub struct ResilverState {
pub target_idx: usize,
pub source_idx: usize,
pub current_block: u64,
pub total_blocks: u64,
pub blocks_copied: u64,
pub errors: u64,
}
impl ResilverState {
pub fn progress(&self) -> f64 {
if self.total_blocks == 0 {
100.0
} else {
(self.blocks_copied as f64 / self.total_blocks as f64) * 100.0
}
}
pub fn is_complete(&self) -> bool {
self.current_block >= self.total_blocks
}
}
pub struct MirrorVdev {
config: MirrorConfig,
children: Vec<MirrorChild>,
block_data: BTreeMap<usize, BTreeMap<u64, Vec<u8>>>,
read_index: usize,
resilver_state: Option<ResilverState>,
capacity: u64,
stats: MirrorStats,
}
#[derive(Debug, Clone, Default)]
pub struct MirrorStats {
pub reads: u64,
pub writes: u64,
pub read_fallbacks: u64,
pub self_heals: u64,
pub blocks_resilvered: u64,
pub bytes_read: u64,
pub bytes_written: u64,
}
impl MirrorVdev {
pub fn new(
device_ids: &[usize],
config: MirrorConfig,
capacity: u64,
) -> Result<Self, MirrorError> {
if device_ids.len() < 2 {
return Err(MirrorError::InsufficientChildren);
}
if device_ids.len() < config.copies {
return Err(MirrorError::InvalidConfig(
"not enough devices for requested copies",
));
}
let children: Vec<MirrorChild> =
device_ids.iter().map(|&id| MirrorChild::new(id)).collect();
Ok(Self {
config,
children,
block_data: BTreeMap::new(),
read_index: 0,
resilver_state: None,
capacity,
stats: MirrorStats::default(),
})
}
pub fn config(&self) -> &MirrorConfig {
&self.config
}
pub fn children(&self) -> &[MirrorChild] {
&self.children
}
pub fn stats(&self) -> &MirrorStats {
&self.stats
}
pub fn capacity(&self) -> u64 {
self.capacity
}
pub fn healthy_count(&self) -> usize {
self.children.iter().filter(|c| c.is_readable()).count()
}
pub fn is_degraded(&self) -> bool {
self.healthy_count() < self.children.len()
}
pub fn can_tolerate_failure(&self) -> bool {
self.healthy_count() >= 2
}
pub fn write_block(&mut self, offset: u64, data: &[u8]) -> Result<(), MirrorError> {
if offset + data.len() as u64 > self.capacity {
return Err(MirrorError::IoError("write beyond capacity"));
}
let mut success_count = 0;
let child_count = self.children.len();
for idx in 0..child_count {
if !self.children[idx].is_writable() {
continue;
}
let child_data = self.block_data.entry(idx).or_default();
child_data.insert(offset, data.to_vec());
success_count += 1;
self.children[idx].bytes_written += data.len() as u64;
}
self.stats.writes += 1;
self.stats.bytes_written += data.len() as u64;
if success_count > 0 {
Ok(())
} else {
Err(MirrorError::IoError("all children failed"))
}
}
fn write_to_child(&mut self, idx: usize, offset: u64, data: &[u8]) {
let child_data = self.block_data.entry(idx).or_default();
child_data.insert(offset, data.to_vec());
}
pub fn read_block(&mut self, offset: u64, size: usize) -> Result<Vec<u8>, MirrorError> {
if offset + size as u64 > self.capacity {
return Err(MirrorError::IoError("read beyond capacity"));
}
let child_count = self.children.len();
let start_idx = if self.config.load_balance_reads {
self.read_index
} else {
0
};
for i in 0..child_count {
let idx = (start_idx + i) % child_count;
if !self.children[idx].is_readable() {
continue;
}
match self.read_from_child(idx, offset, size) {
Ok(data) => {
self.children[idx].bytes_read += size as u64;
self.stats.reads += 1;
self.stats.bytes_read += size as u64;
if self.config.load_balance_reads {
self.read_index = (idx + 1) % child_count;
}
if i > 0 {
self.stats.read_fallbacks += 1;
}
return Ok(data);
}
Err(_) => {
self.children[idx].read_errors += 1;
if self.children[idx].read_errors > 10 {
self.children[idx].state = ChildState::Degraded;
}
continue;
}
}
}
Err(MirrorError::AllChildrenFailed)
}
fn read_from_child(
&self,
idx: usize,
offset: u64,
size: usize,
) -> Result<Vec<u8>, &'static str> {
self.block_data
.get(&idx)
.and_then(|m| m.get(&offset))
.map(|data| {
if data.len() >= size {
data[..size].to_vec()
} else {
let mut result = data.clone();
result.resize(size, 0);
result
}
})
.ok_or("block not found")
}
pub fn read_with_healing(
&mut self,
offset: u64,
size: usize,
expected_checksum: &[u64; 4],
) -> Result<Vec<u8>, MirrorError> {
if offset + size as u64 > self.capacity {
return Err(MirrorError::IoError("read beyond capacity"));
}
let child_count = self.children.len();
let mut good_data: Option<Vec<u8>> = None;
let mut good_idx: Option<usize> = None;
let mut bad_children: Vec<usize> = Vec::new();
for idx in 0..child_count {
if !self.children[idx].is_readable() {
bad_children.push(idx);
continue;
}
match self.read_from_child(idx, offset, size) {
Ok(data) => {
let checksum = Checksum::calculate(&data);
if checksum.matches(expected_checksum) {
good_data = Some(data);
good_idx = Some(idx);
self.children[idx].bytes_read += size as u64;
} else {
self.children[idx].checksum_errors += 1;
bad_children.push(idx);
crate::lcpfs_println!(
"[ MIRROR] Checksum mismatch on child {} at offset {}",
self.children[idx].device_id,
offset
);
}
}
Err(_) => {
self.children[idx].read_errors += 1;
bad_children.push(idx);
}
}
}
self.stats.reads += 1;
if let Some(ref data) = good_data {
if self.config.auto_heal && !bad_children.is_empty() {
for &bad_idx in &bad_children {
if self.children[bad_idx].is_writable() {
self.write_to_child(bad_idx, offset, data);
self.stats.self_heals += 1;
crate::lcpfs_println!(
"[ MIRROR] Healed child {} at offset {} from child {}",
self.children[bad_idx].device_id,
offset,
self.children[good_idx.unwrap()].device_id
);
}
}
}
self.stats.bytes_read += size as u64;
Ok(data.clone())
} else {
Err(MirrorError::AllCopiesCorrupt)
}
}
pub fn start_resilver(
&mut self,
target_device_id: usize,
total_blocks: u64,
) -> Result<(), MirrorError> {
if self.resilver_state.is_some() {
return Err(MirrorError::ResilverInProgress);
}
let target_idx = self
.children
.iter()
.position(|c| c.device_id == target_device_id)
.ok_or(MirrorError::DeviceNotFound(target_device_id))?;
let source_idx = self
.children
.iter()
.position(|c| c.is_readable() && c.device_id != target_device_id)
.ok_or(MirrorError::NoHealthySource)?;
crate::lcpfs_println!(
"[ MIRROR] Starting resilver: copying from child {} to child {}",
self.children[source_idx].device_id,
target_device_id
);
self.children[target_idx].state = ChildState::Resilvering;
self.resilver_state = Some(ResilverState {
target_idx,
source_idx,
current_block: 0,
total_blocks,
blocks_copied: 0,
errors: 0,
});
Ok(())
}
pub fn resilver_step(&mut self, batch_size: u64) -> Result<u64, MirrorError> {
let (source_idx, target_idx, block_size, mut current_block, total_blocks) = {
let state = match &self.resilver_state {
Some(s) => s,
None => return Ok(0),
};
if state.is_complete() {
return Ok(0);
}
(
state.source_idx,
state.target_idx,
self.config.block_size as u64,
state.current_block,
state.total_blocks,
)
};
let mut copied = 0u64;
let mut errors = 0u64;
while copied < batch_size && current_block < total_blocks {
let offset = current_block * block_size;
match self.read_from_child(source_idx, offset, self.config.block_size) {
Ok(data) => {
self.write_to_child(target_idx, offset, &data);
copied += 1;
self.stats.blocks_resilvered += 1;
}
Err(_) => {
errors += 1;
}
}
current_block += 1;
}
if let Some(ref mut state) = self.resilver_state {
state.current_block = current_block;
state.blocks_copied += copied;
state.errors += errors;
if state.is_complete() {
let target_device_id = self.children[target_idx].device_id;
let blocks_copied = state.blocks_copied;
let total_errors = state.errors;
self.children[target_idx].state = ChildState::Online;
self.children[target_idx].read_errors = 0;
self.children[target_idx].write_errors = 0;
self.children[target_idx].checksum_errors = 0;
crate::lcpfs_println!(
"[ MIRROR] Resilver complete for child {} ({} blocks copied, {} errors)",
target_device_id,
blocks_copied,
total_errors
);
self.resilver_state = None;
}
}
Ok(copied)
}
pub fn get_resilver_progress(&self) -> Option<f64> {
self.resilver_state.as_ref().map(|s| s.progress())
}
pub fn cancel_resilver(&mut self) {
if let Some(state) = &self.resilver_state {
let target_idx = state.target_idx;
self.children[target_idx].state = ChildState::Faulted;
}
self.resilver_state = None;
}
pub fn mark_child_faulted(&mut self, device_id: usize) -> Result<(), MirrorError> {
let child = self
.children
.iter_mut()
.find(|c| c.device_id == device_id)
.ok_or(MirrorError::DeviceNotFound(device_id))?;
crate::lcpfs_println!(
"[ MIRROR] Child {} marked as FAULTED (was {:?})",
device_id,
child.state
);
child.state = ChildState::Faulted;
Ok(())
}
pub fn mark_child_online(&mut self, device_id: usize) -> Result<(), MirrorError> {
let child = self
.children
.iter_mut()
.find(|c| c.device_id == device_id)
.ok_or(MirrorError::DeviceNotFound(device_id))?;
child.state = ChildState::Online;
child.read_errors = 0;
child.write_errors = 0;
child.checksum_errors = 0;
Ok(())
}
pub fn add_child(&mut self, device_id: usize) -> Result<(), MirrorError> {
if self.children.iter().any(|c| c.device_id == device_id) {
return Err(MirrorError::InvalidConfig("device already in mirror"));
}
let child = MirrorChild::new(device_id);
self.children.push(child);
crate::lcpfs_println!("[ MIRROR] Added child {} to mirror", device_id);
Ok(())
}
pub fn remove_child(&mut self, device_id: usize) -> Result<(), MirrorError> {
if self.children.len() <= 2 {
return Err(MirrorError::InsufficientChildren);
}
let idx = self
.children
.iter()
.position(|c| c.device_id == device_id)
.ok_or(MirrorError::DeviceNotFound(device_id))?;
self.children.remove(idx);
crate::lcpfs_println!("[ MIRROR] Removed child {} from mirror", device_id);
Ok(())
}
pub fn get_status(&self) -> (usize, usize, usize, usize, usize) {
let online = self
.children
.iter()
.filter(|c| c.state == ChildState::Online)
.count();
let degraded = self
.children
.iter()
.filter(|c| c.state == ChildState::Degraded)
.count();
let faulted = self
.children
.iter()
.filter(|c| c.state == ChildState::Faulted)
.count();
let resilvering = self
.children
.iter()
.filter(|c| c.state == ChildState::Resilvering)
.count();
let offline = self
.children
.iter()
.filter(|c| c.state == ChildState::Offline)
.count();
(online, degraded, faulted, resilvering, offline)
}
}
pub fn create_mirror_2way(
dev1: usize,
dev2: usize,
capacity: u64,
) -> Result<MirrorVdev, MirrorError> {
MirrorVdev::new(&[dev1, dev2], MirrorConfig::two_way(), capacity)
}
pub fn create_mirror_3way(
dev1: usize,
dev2: usize,
dev3: usize,
capacity: u64,
) -> Result<MirrorVdev, MirrorError> {
MirrorVdev::new(&[dev1, dev2, dev3], MirrorConfig::three_way(), capacity)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_mirror_config_default() {
let config = MirrorConfig::default();
assert_eq!(config.copies, 2);
assert_eq!(config.block_size, 4096);
assert!(config.load_balance_reads);
assert!(config.auto_heal);
}
#[test]
fn test_mirror_config_two_way() {
let config = MirrorConfig::two_way();
assert_eq!(config.copies, 2);
}
#[test]
fn test_mirror_config_three_way() {
let config = MirrorConfig::three_way();
assert_eq!(config.copies, 3);
}
#[test]
fn test_mirror_config_with_block_size() {
let config = MirrorConfig::default().with_block_size(128 * 1024);
assert_eq!(config.block_size, 128 * 1024);
}
#[test]
fn test_create_two_way_mirror() {
let mirror = create_mirror_2way(0, 1, 1024 * 1024).expect("should create 2-way mirror");
assert_eq!(mirror.children().len(), 2);
assert_eq!(mirror.capacity(), 1024 * 1024);
}
#[test]
fn test_create_three_way_mirror() {
let mirror = create_mirror_3way(0, 1, 2, 1024 * 1024).expect("should create 3-way mirror");
assert_eq!(mirror.children().len(), 3);
}
#[test]
fn test_create_mirror_insufficient_children() {
let result = MirrorVdev::new(&[0], MirrorConfig::default(), 1024);
assert!(matches!(result, Err(MirrorError::InsufficientChildren)));
}
#[test]
fn test_healthy_count() {
let mirror = create_mirror_2way(0, 1, 1024 * 1024).expect("should create mirror");
assert_eq!(mirror.healthy_count(), 2);
assert!(!mirror.is_degraded());
}
#[test]
fn test_write_block() {
let mut mirror = create_mirror_2way(0, 1, 1024 * 1024).expect("should create mirror");
let data = vec![0xAB; 4096];
mirror.write_block(0, &data).expect("should write");
assert_eq!(mirror.stats().writes, 1);
assert_eq!(mirror.stats().bytes_written, 4096);
}
#[test]
fn test_write_to_all_children() {
let mut mirror = create_mirror_2way(0, 1, 1024 * 1024).expect("should create mirror");
let data = vec![0xAB; 4096];
mirror.write_block(0, &data).expect("should write");
let read1 = mirror
.read_from_child(0, 0, 4096)
.expect("should read from child 0");
let read2 = mirror
.read_from_child(1, 0, 4096)
.expect("should read from child 1");
assert_eq!(read1, data);
assert_eq!(read2, data);
}
#[test]
fn test_write_beyond_capacity() {
let mut mirror = create_mirror_2way(0, 1, 1024).expect("should create mirror");
let data = vec![0xAB; 4096];
let result = mirror.write_block(0, &data);
assert!(matches!(result, Err(MirrorError::IoError(_))));
}
#[test]
fn test_read_block() {
let mut mirror = create_mirror_2way(0, 1, 1024 * 1024).expect("should create mirror");
let data = vec![0xAB; 4096];
mirror.write_block(0, &data).expect("should write");
let read_data = mirror.read_block(0, 4096).expect("should read");
assert_eq!(read_data, data);
}
#[test]
fn test_read_fallback() {
let mut mirror = create_mirror_2way(0, 1, 1024 * 1024).expect("should create mirror");
let data = vec![0xAB; 4096];
mirror.write_block(0, &data).expect("should write");
mirror.block_data.get_mut(&0).unwrap().remove(&0);
let read_data = mirror
.read_block(0, 4096)
.expect("should read from fallback");
assert_eq!(read_data, data);
assert_eq!(mirror.stats().read_fallbacks, 1);
}
#[test]
fn test_read_load_balancing() {
let mut mirror = create_mirror_2way(0, 1, 1024 * 1024).expect("should create mirror");
let data = vec![0xAB; 4096];
mirror.write_block(0, &data).expect("should write");
let initial_idx = mirror.read_index;
mirror.read_block(0, 4096).expect("read 1");
assert_eq!(mirror.read_index, (initial_idx + 1) % 2);
mirror.read_block(0, 4096).expect("read 2");
assert_eq!(mirror.read_index, initial_idx);
}
#[test]
fn test_read_beyond_capacity() {
let mut mirror = create_mirror_2way(0, 1, 1024).expect("should create mirror");
let result = mirror.read_block(0, 4096);
assert!(matches!(result, Err(MirrorError::IoError(_))));
}
#[test]
fn test_read_with_healing_good_data() {
let mut mirror = create_mirror_2way(0, 1, 1024 * 1024).expect("should create mirror");
let data = vec![0xAB; 4096];
let checksum = Checksum::calculate(&data);
mirror.write_block(0, &data).expect("should write");
let read_data = mirror
.read_with_healing(0, 4096, &checksum.value)
.expect("should read with healing");
assert_eq!(read_data, data);
}
#[test]
fn test_read_with_healing_repairs_bad_copy() {
let mut mirror = create_mirror_2way(0, 1, 1024 * 1024).expect("should create mirror");
let data = vec![0xAB; 4096];
let checksum = Checksum::calculate(&data);
mirror.write_block(0, &data).expect("should write");
mirror
.block_data
.get_mut(&0)
.unwrap()
.insert(0, vec![0xFF; 4096]);
let read_data = mirror
.read_with_healing(0, 4096, &checksum.value)
.expect("should read and heal");
assert_eq!(read_data, data);
assert_eq!(mirror.stats().self_heals, 1);
let child0_data = mirror
.read_from_child(0, 0, 4096)
.expect("should read child 0");
assert_eq!(child0_data, data);
}
#[test]
fn test_read_with_healing_all_corrupt() {
let mut mirror = create_mirror_2way(0, 1, 1024 * 1024).expect("should create mirror");
let data = vec![0xAB; 4096];
let good_checksum = Checksum::calculate(&data);
let bad_data = vec![0xFF; 4096];
mirror.write_block(0, &bad_data).expect("should write");
let result = mirror.read_with_healing(0, 4096, &good_checksum.value);
assert!(matches!(result, Err(MirrorError::AllCopiesCorrupt)));
}
#[test]
fn test_start_resilver() {
let mut mirror = create_mirror_2way(0, 1, 4096 * 10).expect("should create mirror");
let data = vec![0xAB; 4096];
for i in 0..10 {
mirror.write_block(i * 4096, &data).expect("should write");
}
mirror.mark_child_faulted(1).expect("should mark faulted");
assert!(mirror.is_degraded());
mirror.start_resilver(1, 10).expect("should start resilver");
assert_eq!(mirror.children[1].state, ChildState::Resilvering);
}
#[test]
fn test_resilver_step() {
let mut mirror = create_mirror_2way(0, 1, 4096 * 10).expect("should create mirror");
let data = vec![0xAB; 4096];
for i in 0..10 {
mirror.write_block(i * 4096, &data).expect("should write");
}
mirror.block_data.remove(&1);
mirror.mark_child_faulted(1).expect("should mark faulted");
mirror.start_resilver(1, 10).expect("should start resilver");
loop {
let copied = mirror.resilver_step(5).expect("should resilver");
if copied == 0 {
break;
}
}
assert_eq!(mirror.children[1].state, ChildState::Online);
assert_eq!(mirror.stats().blocks_resilvered, 10);
for i in 0..10 {
let data1 = mirror
.read_from_child(0, i * 4096, 4096)
.expect("read child 0");
let data2 = mirror
.read_from_child(1, i * 4096, 4096)
.expect("read child 1");
assert_eq!(data1, data2);
}
}
#[test]
fn test_resilver_progress() {
let mut mirror = create_mirror_2way(0, 1, 4096 * 100).expect("should create mirror");
let data = vec![0xAB; 4096];
for i in 0..100 {
mirror.write_block(i * 4096, &data).expect("should write");
}
mirror.mark_child_faulted(1).expect("should mark faulted");
mirror
.start_resilver(1, 100)
.expect("should start resilver");
assert_eq!(mirror.get_resilver_progress(), Some(0.0));
mirror.resilver_step(50).expect("should resilver");
let progress = mirror
.get_resilver_progress()
.expect("should have progress");
assert!(progress >= 50.0);
}
#[test]
fn test_resilver_already_in_progress() {
let mut mirror = create_mirror_3way(0, 1, 2, 4096 * 10).expect("should create mirror");
mirror.mark_child_faulted(1).expect("should mark faulted");
mirror.start_resilver(1, 10).expect("should start resilver");
let result = mirror.start_resilver(2, 10);
assert!(matches!(result, Err(MirrorError::ResilverInProgress)));
}
#[test]
fn test_cancel_resilver() {
let mut mirror = create_mirror_2way(0, 1, 4096 * 10).expect("should create mirror");
mirror.mark_child_faulted(1).expect("should mark faulted");
mirror.start_resilver(1, 10).expect("should start resilver");
mirror.cancel_resilver();
assert!(mirror.get_resilver_progress().is_none());
assert_eq!(mirror.children[1].state, ChildState::Faulted);
}
#[test]
fn test_mark_child_faulted() {
let mut mirror = create_mirror_2way(0, 1, 1024 * 1024).expect("should create mirror");
mirror.mark_child_faulted(0).expect("should mark faulted");
assert_eq!(mirror.children[0].state, ChildState::Faulted);
assert!(mirror.is_degraded());
}
#[test]
fn test_mark_child_online() {
let mut mirror = create_mirror_2way(0, 1, 1024 * 1024).expect("should create mirror");
mirror.mark_child_faulted(0).expect("should mark faulted");
mirror.mark_child_online(0).expect("should mark online");
assert_eq!(mirror.children[0].state, ChildState::Online);
assert!(!mirror.is_degraded());
}
#[test]
fn test_add_child() {
let mut mirror = create_mirror_2way(0, 1, 1024 * 1024).expect("should create mirror");
mirror.add_child(2).expect("should add child");
assert_eq!(mirror.children().len(), 3);
}
#[test]
fn test_add_duplicate_child() {
let mut mirror = create_mirror_2way(0, 1, 1024 * 1024).expect("should create mirror");
let result = mirror.add_child(0);
assert!(matches!(result, Err(MirrorError::InvalidConfig(_))));
}
#[test]
fn test_remove_child() {
let mut mirror = create_mirror_3way(0, 1, 2, 1024 * 1024).expect("should create mirror");
mirror.remove_child(2).expect("should remove child");
assert_eq!(mirror.children().len(), 2);
}
#[test]
fn test_remove_child_insufficient() {
let mut mirror = create_mirror_2way(0, 1, 1024 * 1024).expect("should create mirror");
let result = mirror.remove_child(0);
assert!(matches!(result, Err(MirrorError::InsufficientChildren)));
}
#[test]
fn test_get_status() {
let mut mirror = create_mirror_3way(0, 1, 2, 1024 * 1024).expect("should create mirror");
mirror.mark_child_faulted(2).expect("should mark faulted");
let (online, degraded, faulted, resilvering, offline) = mirror.get_status();
assert_eq!(online, 2);
assert_eq!(degraded, 0);
assert_eq!(faulted, 1);
assert_eq!(resilvering, 0);
assert_eq!(offline, 0);
}
#[test]
fn test_can_tolerate_failure_2way() {
let mut mirror = create_mirror_2way(0, 1, 1024 * 1024).expect("should create mirror");
assert!(mirror.can_tolerate_failure());
mirror.mark_child_faulted(0).expect("should mark faulted");
assert!(!mirror.can_tolerate_failure());
}
#[test]
fn test_can_tolerate_failure_3way() {
let mut mirror = create_mirror_3way(0, 1, 2, 1024 * 1024).expect("should create mirror");
assert!(mirror.can_tolerate_failure());
mirror.mark_child_faulted(0).expect("should mark faulted");
assert!(mirror.can_tolerate_failure());
mirror.mark_child_faulted(1).expect("should mark faulted");
assert!(!mirror.can_tolerate_failure());
}
#[test]
fn test_write_with_one_faulted() {
let mut mirror = create_mirror_2way(0, 1, 1024 * 1024).expect("should create mirror");
mirror.mark_child_faulted(0).expect("should mark faulted");
let data = vec![0xAB; 4096];
mirror
.write_block(0, &data)
.expect("should write to surviving child");
let read_data = mirror.read_block(0, 4096).expect("should read");
assert_eq!(read_data, data);
}
#[test]
fn test_read_with_one_faulted() {
let mut mirror = create_mirror_2way(0, 1, 1024 * 1024).expect("should create mirror");
let data = vec![0xAB; 4096];
mirror.write_block(0, &data).expect("should write");
mirror.mark_child_faulted(0).expect("should mark faulted");
let read_data = mirror
.read_block(0, 4096)
.expect("should read from surviving child");
assert_eq!(read_data, data);
}
#[test]
fn test_stats_tracking() {
let mut mirror = create_mirror_2way(0, 1, 1024 * 1024).expect("should create mirror");
let data = vec![0xAB; 4096];
mirror.write_block(0, &data).expect("should write");
mirror.read_block(0, 4096).expect("should read");
let stats = mirror.stats();
assert_eq!(stats.writes, 1);
assert_eq!(stats.reads, 1);
assert_eq!(stats.bytes_written, 4096);
assert_eq!(stats.bytes_read, 4096);
}
#[test]
fn test_child_stats() {
let mut mirror = create_mirror_2way(0, 1, 1024 * 1024).expect("should create mirror");
let data = vec![0xAB; 4096];
mirror.write_block(0, &data).expect("should write");
mirror.read_block(0, 4096).expect("should read");
assert_eq!(mirror.children[0].bytes_written, 4096);
assert_eq!(mirror.children[1].bytes_written, 4096);
let total_read = mirror.children[0].bytes_read + mirror.children[1].bytes_read;
assert_eq!(total_read, 4096);
}
}