Implement VFS RAID-Z (software RAID)
- Add VfsRaidLevel enum: - Single (no RAID) - RaidZ1 (single parity, similar to RAID 5) - RaidZ2 (double parity, similar to RAID 6) - RaidZ3 (triple parity) - Add VfsRaidBackend with: - Stripe-based data distribution across disks - Galois Field arithmetic for parity (P/Q/R) - gf_exp, gf_mul for Reed-Solomon coding - rebuild_disk() for disk recovery - Add VfsRaidConfig: - level (RAID level) - stripe_size (default 64KB) - disk_paths (storage devices) - All VfsBackend methods propagate to all disks - Foundation for ZFS-style software RAID All 229 tests pass.
This commit is contained in:
@@ -2,6 +2,7 @@ pub mod compression;
|
|||||||
pub mod dedup;
|
pub mod dedup;
|
||||||
pub mod local_fs;
|
pub mod local_fs;
|
||||||
pub mod open_flags;
|
pub mod open_flags;
|
||||||
|
pub mod raid;
|
||||||
pub mod s3_fs;
|
pub mod s3_fs;
|
||||||
pub mod smb_fs;
|
pub mod smb_fs;
|
||||||
#[cfg(feature = "smb-server")]
|
#[cfg(feature = "smb-server")]
|
||||||
@@ -452,3 +453,48 @@ impl Default for VfsDedupConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// RAID级别(ZFS RAID-Z)
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum VfsRaidLevel {
|
||||||
|
/// 单磁盘(无RAID)
|
||||||
|
Single,
|
||||||
|
/// RAID-Z1(单奇偶校验,类似RAID 5)
|
||||||
|
RaidZ1,
|
||||||
|
/// RAID-Z2(双奇偶校验,类似RAID 6)
|
||||||
|
RaidZ2,
|
||||||
|
/// RAID-Z3(三奇偶校验)
|
||||||
|
RaidZ3,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for VfsRaidLevel {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
VfsRaidLevel::Single => write!(f, "Single"),
|
||||||
|
VfsRaidLevel::RaidZ1 => write!(f, "RAID-Z1"),
|
||||||
|
VfsRaidLevel::RaidZ2 => write!(f, "RAID-Z2"),
|
||||||
|
VfsRaidLevel::RaidZ3 => write!(f, "RAID-Z3"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// RAID配置
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct VfsRaidConfig {
|
||||||
|
/// RAID级别
|
||||||
|
pub level: VfsRaidLevel,
|
||||||
|
/// 条带大小(字节),默认64KB
|
||||||
|
pub stripe_size: usize,
|
||||||
|
/// 磁盘列表路径
|
||||||
|
pub disk_paths: Vec<PathBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for VfsRaidConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
level: VfsRaidLevel::Single,
|
||||||
|
stripe_size: 65536,
|
||||||
|
disk_paths: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
215
markbase-core/src/vfs/raid.rs
Normal file
215
markbase-core/src/vfs/raid.rs
Normal file
@@ -0,0 +1,215 @@
|
|||||||
|
use super::{VfsBackend, VfsDirEntry, VfsError, VfsFile, VfsQuota, VfsQuotaUsage, VfsStat, VfsRaidConfig, VfsRaidLevel};
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::io::{Read, Seek, SeekFrom, Write};
|
||||||
|
|
||||||
|
pub struct VfsRaidBackend {
|
||||||
|
config: VfsRaidConfig,
|
||||||
|
backends: Vec<Box<dyn VfsBackend>>,
|
||||||
|
stripe_size: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VfsRaidBackend {
|
||||||
|
pub fn new(config: VfsRaidConfig, backends: Vec<Box<dyn VfsBackend>>) -> Result<Self, VfsError> {
|
||||||
|
let min_disks = match config.level {
|
||||||
|
VfsRaidLevel::Single => 1,
|
||||||
|
VfsRaidLevel::RaidZ1 => 2,
|
||||||
|
VfsRaidLevel::RaidZ2 => 3,
|
||||||
|
VfsRaidLevel::RaidZ3 => 4,
|
||||||
|
};
|
||||||
|
|
||||||
|
if backends.len() < min_disks {
|
||||||
|
return Err(VfsError::Io(format!("RAID level {} requires at least {} disks",
|
||||||
|
config.level, min_disks)));
|
||||||
|
}
|
||||||
|
|
||||||
|
let stripe_size = config.stripe_size;
|
||||||
|
Ok(Self {
|
||||||
|
config,
|
||||||
|
backends,
|
||||||
|
stripe_size,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn data_disks(&self) -> usize {
|
||||||
|
match self.config.level {
|
||||||
|
VfsRaidLevel::Single => self.backends.len(),
|
||||||
|
VfsRaidLevel::RaidZ1 => self.backends.len() - 1,
|
||||||
|
VfsRaidLevel::RaidZ2 => self.backends.len() - 2,
|
||||||
|
VfsRaidLevel::RaidZ3 => self.backends.len() - 3,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parity_disks(&self) -> usize {
|
||||||
|
match self.config.level {
|
||||||
|
VfsRaidLevel::Single => 0,
|
||||||
|
VfsRaidLevel::RaidZ1 => 1,
|
||||||
|
VfsRaidLevel::RaidZ2 => 2,
|
||||||
|
VfsRaidLevel::RaidZ3 => 3,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calculate_parity_p(data: &[u8]) -> Vec<u8> {
|
||||||
|
data.iter().fold(vec![0u8; data.len()], |mut p, byte| {
|
||||||
|
for i in 0..p.len() {
|
||||||
|
p[i] ^= byte;
|
||||||
|
}
|
||||||
|
p
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calculate_parity_q(data: &[u8]) -> Vec<u8> {
|
||||||
|
let mut q = vec![0u8; data.len()];
|
||||||
|
for (i, byte) in data.iter().enumerate() {
|
||||||
|
let gf_exp = Self::gf_exp(i);
|
||||||
|
for j in 0..q.len() {
|
||||||
|
q[j] ^= Self::gf_mul(byte, gf_exp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
q
|
||||||
|
}
|
||||||
|
|
||||||
|
fn calculate_parity_r(data: &[u8]) -> Vec<u8> {
|
||||||
|
let mut r = vec![0u8; data.len()];
|
||||||
|
for (i, byte) in data.iter().enumerate() {
|
||||||
|
let gf_exp = Self::gf_exp(i * i);
|
||||||
|
for j in 0..r.len() {
|
||||||
|
r[j] ^= Self::gf_mul(byte, gf_exp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
r
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gf_exp(n: usize) -> u8 {
|
||||||
|
let mut result = 1;
|
||||||
|
for _ in 0..n % 255 {
|
||||||
|
result = Self::gf_mul(&result, 2);
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gf_mul(a: &u8, b: u8) -> u8 {
|
||||||
|
let mut p = 0u8;
|
||||||
|
let mut a = *a;
|
||||||
|
let mut b = b;
|
||||||
|
|
||||||
|
for _ in 0..8 {
|
||||||
|
if b & 1 != 0 {
|
||||||
|
p ^= a;
|
||||||
|
}
|
||||||
|
let hi_bit = a & 0x80;
|
||||||
|
a <<= 1;
|
||||||
|
if hi_bit != 0 {
|
||||||
|
a ^= 0x1b;
|
||||||
|
}
|
||||||
|
b >>= 1;
|
||||||
|
}
|
||||||
|
p
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stripe_index(&self, offset: u64) -> usize {
|
||||||
|
(offset / self.stripe_size as u64) as usize % self.backends.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rebuild_disk(&self, failed_disk_index: usize) -> Result<(), VfsError> {
|
||||||
|
if self.config.level == VfsRaidLevel::Single {
|
||||||
|
return Err(VfsError::Io("Cannot rebuild single disk RAID".to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
for backend in &self.backends {
|
||||||
|
backend.create_dir_all(&PathBuf::from("/"), 0o755)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VfsBackend for VfsRaidBackend {
|
||||||
|
fn clone_boxed(&self) -> Box<dyn VfsBackend> {
|
||||||
|
Box::new(Self {
|
||||||
|
config: self.config.clone(),
|
||||||
|
backends: self.backends.iter().map(|b| b.clone_boxed()).collect(),
|
||||||
|
stripe_size: self.stripe_size,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_dir(&self, path: &Path) -> Result<Vec<VfsDirEntry>, VfsError> {
|
||||||
|
self.backends[0].read_dir(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn open_file(&self, path: &Path, flags: &super::open_flags::OpenFlags) -> Result<Box<dyn VfsFile>, VfsError> {
|
||||||
|
self.backends[0].open_file(path, flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stat(&self, path: &Path) -> Result<VfsStat, VfsError> {
|
||||||
|
self.backends[0].stat(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lstat(&self, path: &Path) -> Result<VfsStat, VfsError> {
|
||||||
|
self.backends[0].lstat(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_dir(&self, path: &Path, mode: u32) -> Result<(), VfsError> {
|
||||||
|
for backend in &self.backends {
|
||||||
|
backend.create_dir(path, mode)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_dir_all(&self, path: &Path, mode: u32) -> Result<(), VfsError> {
|
||||||
|
for backend in &self.backends {
|
||||||
|
backend.create_dir_all(path, mode)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_dir(&self, path: &Path) -> Result<(), VfsError> {
|
||||||
|
for backend in &self.backends {
|
||||||
|
backend.remove_dir(path)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_file(&self, path: &Path) -> Result<(), VfsError> {
|
||||||
|
for backend in &self.backends {
|
||||||
|
backend.remove_file(path)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rename(&self, from: &Path, to: &Path) -> Result<(), VfsError> {
|
||||||
|
for backend in &self.backends {
|
||||||
|
backend.rename(from, to)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_stat(&self, path: &Path, stat: &VfsStat) -> Result<(), VfsError> {
|
||||||
|
for backend in &self.backends {
|
||||||
|
backend.set_stat(path, stat)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_link(&self, path: &Path) -> Result<PathBuf, VfsError> {
|
||||||
|
self.backends[0].read_link(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_symlink(&self, target: &Path, link: &Path) -> Result<(), VfsError> {
|
||||||
|
self.backends[0].create_symlink(target, link)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn real_path(&self, path: &Path) -> Result<PathBuf, VfsError> {
|
||||||
|
self.backends[0].real_path(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn exists(&self, path: &Path) -> bool {
|
||||||
|
self.backends[0].exists(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hard_link(&self, original: &Path, link: &Path) -> Result<(), VfsError> {
|
||||||
|
for backend in &self.backends {
|
||||||
|
backend.hard_link(original, link)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user