mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-10 07:06:53 +00:00
feat: implement complete ahm heal system with ecstore integration
- Add comprehensive heal storage API with ECStore integration - Implement heal object, bucket, disk, metadata, and EC decode operations - Add heal task management with progress tracking and statistics - Optimize heal manager by removing unnecessary workers - Add integration tests for core heal functionality (heal_object, heal_bucket, heal_format) - Integrate with ecstore's native heal commands for actual repair operations Signed-off-by: junxiang Mu <1948535941@qq.com>
This commit is contained in:
+54
-111
@@ -57,43 +57,43 @@ impl Default for HealConfig {
|
||||
}
|
||||
}
|
||||
|
||||
/// Heal 状态
|
||||
/// Heal state
|
||||
#[derive(Debug, Default)]
|
||||
pub struct HealState {
|
||||
/// 是否正在运行
|
||||
/// Whether running
|
||||
pub is_running: bool,
|
||||
/// 当前 heal 周期
|
||||
/// Current heal cycle
|
||||
pub current_cycle: u64,
|
||||
/// 最后 heal 时间
|
||||
/// Last heal time
|
||||
pub last_heal_time: Option<SystemTime>,
|
||||
/// 总 heal 对象数
|
||||
/// Total healed objects
|
||||
pub total_healed_objects: u64,
|
||||
/// 总 heal 失败数
|
||||
/// Total heal failures
|
||||
pub total_heal_failures: u64,
|
||||
/// 当前活跃 heal 任务数
|
||||
/// Current active heal tasks
|
||||
pub active_heal_count: usize,
|
||||
}
|
||||
|
||||
/// Heal 管理器
|
||||
/// Heal manager
|
||||
pub struct HealManager {
|
||||
/// Heal 配置
|
||||
/// Heal config
|
||||
config: Arc<RwLock<HealConfig>>,
|
||||
/// Heal 状态
|
||||
/// Heal state
|
||||
state: Arc<RwLock<HealState>>,
|
||||
/// 活跃的 heal 任务
|
||||
/// Active heal tasks
|
||||
active_heals: Arc<Mutex<HashMap<String, Arc<HealTask>>>>,
|
||||
/// Heal 队列
|
||||
/// Heal queue
|
||||
heal_queue: Arc<Mutex<VecDeque<HealRequest>>>,
|
||||
/// 存储层接口
|
||||
/// Storage layer interface
|
||||
storage: Arc<dyn HealStorageAPI>,
|
||||
/// 取消令牌
|
||||
/// Cancel token
|
||||
cancel_token: CancellationToken,
|
||||
/// 统计信息
|
||||
/// Statistics
|
||||
statistics: Arc<RwLock<HealStatistics>>,
|
||||
}
|
||||
|
||||
impl HealManager {
|
||||
/// 创建新的 HealManager
|
||||
/// Create new HealManager
|
||||
pub fn new(storage: Arc<dyn HealStorageAPI>, config: Option<HealConfig>) -> Self {
|
||||
let config = config.unwrap_or_default();
|
||||
Self {
|
||||
@@ -107,7 +107,7 @@ impl HealManager {
|
||||
}
|
||||
}
|
||||
|
||||
/// 启动 HealManager
|
||||
/// Start HealManager
|
||||
pub async fn start(&self) -> Result<()> {
|
||||
let mut state = self.state.write().await;
|
||||
if state.is_running {
|
||||
@@ -119,24 +119,21 @@ impl HealManager {
|
||||
|
||||
info!("Starting HealManager");
|
||||
|
||||
// 启动调度器
|
||||
// start scheduler
|
||||
self.start_scheduler().await?;
|
||||
|
||||
// 启动工作器
|
||||
self.start_workers().await?;
|
||||
|
||||
info!("HealManager started successfully");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 停止 HealManager
|
||||
/// Stop HealManager
|
||||
pub async fn stop(&self) -> Result<()> {
|
||||
info!("Stopping HealManager");
|
||||
|
||||
// 取消所有任务
|
||||
// cancel all tasks
|
||||
self.cancel_token.cancel();
|
||||
|
||||
// 等待所有任务完成
|
||||
// wait for all tasks to complete
|
||||
let mut active_heals = self.active_heals.lock().await;
|
||||
for task in active_heals.values() {
|
||||
if let Err(e) = task.cancel().await {
|
||||
@@ -145,7 +142,7 @@ impl HealManager {
|
||||
}
|
||||
active_heals.clear();
|
||||
|
||||
// 更新状态
|
||||
// update state
|
||||
let mut state = self.state.write().await;
|
||||
state.is_running = false;
|
||||
|
||||
@@ -153,7 +150,7 @@ impl HealManager {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 提交 heal 请求
|
||||
/// Submit heal request
|
||||
pub async fn submit_heal_request(&self, request: HealRequest) -> Result<String> {
|
||||
let config = self.config.read().await;
|
||||
let mut queue = self.heal_queue.lock().await;
|
||||
@@ -172,7 +169,7 @@ impl HealManager {
|
||||
Ok(request_id)
|
||||
}
|
||||
|
||||
/// 获取任务状态
|
||||
/// Get task status
|
||||
pub async fn get_task_status(&self, task_id: &str) -> Result<HealTaskStatus> {
|
||||
let active_heals = self.active_heals.lock().await;
|
||||
if let Some(task) = active_heals.get(task_id) {
|
||||
@@ -184,7 +181,7 @@ impl HealManager {
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取任务进度
|
||||
/// Get task progress
|
||||
pub async fn get_task_progress(&self, task_id: &str) -> Result<HealProgress> {
|
||||
let active_heals = self.active_heals.lock().await;
|
||||
if let Some(task) = active_heals.get(task_id) {
|
||||
@@ -196,7 +193,7 @@ impl HealManager {
|
||||
}
|
||||
}
|
||||
|
||||
/// 取消任务
|
||||
/// Cancel task
|
||||
pub async fn cancel_task(&self, task_id: &str) -> Result<()> {
|
||||
let mut active_heals = self.active_heals.lock().await;
|
||||
if let Some(task) = active_heals.get(task_id) {
|
||||
@@ -211,24 +208,24 @@ impl HealManager {
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取统计信息
|
||||
/// Get statistics
|
||||
pub async fn get_statistics(&self) -> HealStatistics {
|
||||
self.statistics.read().await.clone()
|
||||
}
|
||||
|
||||
/// 获取活跃任务数量
|
||||
/// Get active task count
|
||||
pub async fn get_active_task_count(&self) -> usize {
|
||||
let active_heals = self.active_heals.lock().await;
|
||||
active_heals.len()
|
||||
}
|
||||
|
||||
/// 获取队列长度
|
||||
/// Get queue length
|
||||
pub async fn get_queue_length(&self) -> usize {
|
||||
let queue = self.heal_queue.lock().await;
|
||||
queue.len()
|
||||
}
|
||||
|
||||
/// 启动调度器
|
||||
/// Start scheduler
|
||||
async fn start_scheduler(&self) -> Result<()> {
|
||||
let config = self.config.clone();
|
||||
let heal_queue = self.heal_queue.clone();
|
||||
@@ -256,78 +253,7 @@ impl HealManager {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 启动工作器
|
||||
async fn start_workers(&self) -> Result<()> {
|
||||
let config = self.config.clone();
|
||||
let active_heals = self.active_heals.clone();
|
||||
let storage = self.storage.clone();
|
||||
let cancel_token = self.cancel_token.clone();
|
||||
let statistics = self.statistics.clone();
|
||||
|
||||
let worker_count = config.read().await.max_concurrent_heals;
|
||||
|
||||
for worker_id in 0..worker_count {
|
||||
let active_heals = active_heals.clone();
|
||||
let _storage = storage.clone();
|
||||
let cancel_token = cancel_token.clone();
|
||||
let statistics = statistics.clone();
|
||||
|
||||
tokio::spawn(async move {
|
||||
info!("Starting heal worker {}", worker_id);
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
_ = cancel_token.cancelled() => {
|
||||
info!("Heal worker {} received shutdown signal", worker_id);
|
||||
break;
|
||||
}
|
||||
_ = async {
|
||||
// 等待任务
|
||||
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||
} => {
|
||||
// 检查是否有可执行的任务
|
||||
let mut active_heals_guard = active_heals.lock().await;
|
||||
let mut completed_tasks = Vec::new();
|
||||
|
||||
for (id, task) in active_heals_guard.iter() {
|
||||
let status = task.get_status().await;
|
||||
if matches!(status, HealTaskStatus::Completed | HealTaskStatus::Failed { .. } | HealTaskStatus::Cancelled) {
|
||||
completed_tasks.push(id.clone());
|
||||
}
|
||||
}
|
||||
|
||||
// 移除已完成的任务
|
||||
for task_id in completed_tasks {
|
||||
if let Some(task) = active_heals_guard.remove(&task_id) {
|
||||
// 更新统计信息
|
||||
let mut stats = statistics.write().await;
|
||||
match task.get_status().await {
|
||||
HealTaskStatus::Completed => {
|
||||
stats.update_task_completion(true);
|
||||
}
|
||||
HealTaskStatus::Failed { .. } => {
|
||||
stats.update_task_completion(false);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 更新活跃任务数量
|
||||
let mut stats = statistics.write().await;
|
||||
stats.update_running_tasks(active_heals_guard.len() as u64);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
info!("Heal worker {} stopped", worker_id);
|
||||
});
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 处理 heal 队列
|
||||
/// Process heal queue
|
||||
async fn process_heal_queue(
|
||||
heal_queue: &Arc<Mutex<VecDeque<HealRequest>>>,
|
||||
active_heals: &Arc<Mutex<HashMap<String, Arc<HealTask>>>>,
|
||||
@@ -336,10 +262,10 @@ impl HealManager {
|
||||
storage: &Arc<dyn HealStorageAPI>,
|
||||
) {
|
||||
let config = config.read().await;
|
||||
let mut active_heals = active_heals.lock().await;
|
||||
let mut active_heals_guard = active_heals.lock().await;
|
||||
|
||||
// 检查是否可以启动新的 heal 任务
|
||||
if active_heals.len() >= config.max_concurrent_heals {
|
||||
// check if new heal tasks can be started
|
||||
if active_heals_guard.len() >= config.max_concurrent_heals {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -347,9 +273,12 @@ impl HealManager {
|
||||
if let Some(request) = queue.pop_front() {
|
||||
let task = Arc::new(HealTask::from_request(request, storage.clone()));
|
||||
let task_id = task.id.clone();
|
||||
active_heals.insert(task_id.clone(), task.clone());
|
||||
active_heals_guard.insert(task_id.clone(), task.clone());
|
||||
drop(active_heals_guard);
|
||||
let active_heals_clone = active_heals.clone();
|
||||
let statistics_clone = statistics.clone();
|
||||
|
||||
// 启动 heal 任务
|
||||
// start heal task
|
||||
tokio::spawn(async move {
|
||||
info!("Starting heal task: {}", task_id);
|
||||
match task.execute().await {
|
||||
@@ -360,9 +289,23 @@ impl HealManager {
|
||||
error!("Heal task failed: {} - {}", task_id, e);
|
||||
}
|
||||
}
|
||||
let mut active_heals_guard = active_heals_clone.lock().await;
|
||||
if let Some(completed_task) = active_heals_guard.remove(&task_id) {
|
||||
// update statistics
|
||||
let mut stats = statistics_clone.write().await;
|
||||
match completed_task.get_status().await {
|
||||
HealTaskStatus::Completed => {
|
||||
stats.update_task_completion(true);
|
||||
}
|
||||
_ => {
|
||||
stats.update_task_completion(false);
|
||||
}
|
||||
}
|
||||
stats.update_running_tasks(active_heals_guard.len() as u64);
|
||||
}
|
||||
});
|
||||
|
||||
// 更新统计信息
|
||||
// update statistics
|
||||
let mut stats = statistics.write().await;
|
||||
stats.total_tasks += 1;
|
||||
}
|
||||
|
||||
@@ -17,23 +17,23 @@ use std::time::SystemTime;
|
||||
|
||||
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
|
||||
pub struct HealProgress {
|
||||
/// 已扫描对象数
|
||||
/// Objects scanned
|
||||
pub objects_scanned: u64,
|
||||
/// 已修复对象数
|
||||
/// Objects healed
|
||||
pub objects_healed: u64,
|
||||
/// 修复失败对象数
|
||||
/// Objects failed
|
||||
pub objects_failed: u64,
|
||||
/// 已处理字节数
|
||||
/// Bytes processed
|
||||
pub bytes_processed: u64,
|
||||
/// 当前处理的对象
|
||||
/// Current object
|
||||
pub current_object: Option<String>,
|
||||
/// 进度百分比
|
||||
/// Progress percentage
|
||||
pub progress_percentage: f64,
|
||||
/// 开始时间
|
||||
/// Start time
|
||||
pub start_time: Option<SystemTime>,
|
||||
/// 最后更新时间
|
||||
/// Last update time
|
||||
pub last_update_time: Option<SystemTime>,
|
||||
/// 预计完成时间
|
||||
/// Estimated completion time
|
||||
pub estimated_completion_time: Option<SystemTime>,
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ impl HealProgress {
|
||||
self.bytes_processed = bytes;
|
||||
self.last_update_time = Some(SystemTime::now());
|
||||
|
||||
// 计算进度百分比
|
||||
// calculate progress percentage
|
||||
let total = scanned + healed + failed;
|
||||
if total > 0 {
|
||||
self.progress_percentage = (healed as f64 / total as f64) * 100.0;
|
||||
@@ -81,19 +81,19 @@ impl HealProgress {
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct HealStatistics {
|
||||
/// 总 heal 任务数
|
||||
/// Total heal tasks
|
||||
pub total_tasks: u64,
|
||||
/// 成功完成的任务数
|
||||
/// Successful tasks
|
||||
pub successful_tasks: u64,
|
||||
/// 失败的任务数
|
||||
/// Failed tasks
|
||||
pub failed_tasks: u64,
|
||||
/// 正在运行的任务数
|
||||
/// Running tasks
|
||||
pub running_tasks: u64,
|
||||
/// 总修复对象数
|
||||
/// Total healed objects
|
||||
pub total_objects_healed: u64,
|
||||
/// 总修复字节数
|
||||
/// Total healed bytes
|
||||
pub total_bytes_healed: u64,
|
||||
/// 最后更新时间
|
||||
/// Last update time
|
||||
pub last_update_time: SystemTime,
|
||||
}
|
||||
|
||||
|
||||
+232
-91
@@ -16,82 +16,96 @@ use crate::error::{Error, Result};
|
||||
use async_trait::async_trait;
|
||||
use rustfs_ecstore::{
|
||||
disk::endpoint::Endpoint,
|
||||
heal::heal_commands::{HealOpts, HEAL_DEEP_SCAN, HEAL_NORMAL_SCAN},
|
||||
store_api::{BucketInfo, StorageAPI, ObjectIO},
|
||||
store::ECStore,
|
||||
};
|
||||
use rustfs_madmin::heal_commands::HealResultItem;
|
||||
use std::sync::Arc;
|
||||
use tracing::{debug, error, info};
|
||||
use tracing::{debug, error, info, warn};
|
||||
|
||||
/// 磁盘状态
|
||||
/// Disk status for heal operations
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum DiskStatus {
|
||||
/// 正常
|
||||
/// Ok
|
||||
Ok,
|
||||
/// 离线
|
||||
/// Offline
|
||||
Offline,
|
||||
/// 损坏
|
||||
/// Corrupt
|
||||
Corrupt,
|
||||
/// 缺失
|
||||
/// Missing
|
||||
Missing,
|
||||
/// 权限错误
|
||||
/// Permission denied
|
||||
PermissionDenied,
|
||||
/// 故障
|
||||
/// Faulty
|
||||
Faulty,
|
||||
/// 根挂载
|
||||
/// Root mount
|
||||
RootMount,
|
||||
/// 未知
|
||||
/// Unknown
|
||||
Unknown,
|
||||
/// 未格式化
|
||||
/// Unformatted
|
||||
Unformatted,
|
||||
}
|
||||
|
||||
/// Heal 存储层接口
|
||||
/// Heal storage layer interface
|
||||
#[async_trait]
|
||||
pub trait HealStorageAPI: Send + Sync {
|
||||
/// 获取对象元数据
|
||||
/// Get object meta
|
||||
async fn get_object_meta(&self, bucket: &str, object: &str) -> Result<Option<rustfs_ecstore::store_api::ObjectInfo>>;
|
||||
|
||||
/// 获取对象数据
|
||||
/// Get object data
|
||||
async fn get_object_data(&self, bucket: &str, object: &str) -> Result<Option<Vec<u8>>>;
|
||||
|
||||
/// 写入对象数据
|
||||
/// Put object data
|
||||
async fn put_object_data(&self, bucket: &str, object: &str, data: &[u8]) -> Result<()>;
|
||||
|
||||
/// 删除对象
|
||||
/// Delete object
|
||||
async fn delete_object(&self, bucket: &str, object: &str) -> Result<()>;
|
||||
|
||||
/// 检查对象完整性
|
||||
/// Check object integrity
|
||||
async fn verify_object_integrity(&self, bucket: &str, object: &str) -> Result<bool>;
|
||||
|
||||
/// EC 解码重建
|
||||
/// EC decode rebuild
|
||||
async fn ec_decode_rebuild(&self, bucket: &str, object: &str) -> Result<Vec<u8>>;
|
||||
|
||||
/// 获取磁盘状态
|
||||
/// Get disk status
|
||||
async fn get_disk_status(&self, endpoint: &Endpoint) -> Result<DiskStatus>;
|
||||
|
||||
/// 格式化磁盘
|
||||
/// Format disk
|
||||
async fn format_disk(&self, endpoint: &Endpoint) -> Result<()>;
|
||||
|
||||
/// 获取桶信息
|
||||
/// Get bucket info
|
||||
async fn get_bucket_info(&self, bucket: &str) -> Result<Option<BucketInfo>>;
|
||||
|
||||
/// 修复桶元数据
|
||||
/// Fix bucket metadata
|
||||
async fn heal_bucket_metadata(&self, bucket: &str) -> Result<()>;
|
||||
|
||||
/// 获取所有桶列表
|
||||
/// Get all buckets
|
||||
async fn list_buckets(&self) -> Result<Vec<BucketInfo>>;
|
||||
|
||||
/// 检查对象是否存在
|
||||
/// Check object exists
|
||||
async fn object_exists(&self, bucket: &str, object: &str) -> Result<bool>;
|
||||
|
||||
/// 获取对象大小
|
||||
/// Get object size
|
||||
async fn get_object_size(&self, bucket: &str, object: &str) -> Result<Option<u64>>;
|
||||
|
||||
/// 获取对象校验和
|
||||
/// Get object checksum
|
||||
async fn get_object_checksum(&self, bucket: &str, object: &str) -> Result<Option<String>>;
|
||||
|
||||
/// Heal object using ecstore
|
||||
async fn heal_object(&self, bucket: &str, object: &str, version_id: Option<&str>, opts: &HealOpts) -> Result<(HealResultItem, Option<Error>)>;
|
||||
|
||||
/// Heal bucket using ecstore
|
||||
async fn heal_bucket(&self, bucket: &str, opts: &HealOpts) -> Result<HealResultItem>;
|
||||
|
||||
/// Heal format using ecstore
|
||||
async fn heal_format(&self, dry_run: bool) -> Result<(HealResultItem, Option<Error>)>;
|
||||
|
||||
/// List objects for healing
|
||||
async fn list_objects_for_heal(&self, bucket: &str, prefix: &str) -> Result<Vec<String>>;
|
||||
}
|
||||
|
||||
/// ECStore Heal 存储层实现
|
||||
/// ECStore Heal storage layer implementation
|
||||
pub struct ECStoreHealStorage {
|
||||
ecstore: Arc<ECStore>,
|
||||
}
|
||||
@@ -119,7 +133,7 @@ impl HealStorageAPI for ECStoreHealStorage {
|
||||
async fn get_object_data(&self, bucket: &str, object: &str) -> Result<Option<Vec<u8>>> {
|
||||
debug!("Getting object data: {}/{}", bucket, object);
|
||||
|
||||
match self.ecstore.get_object_reader(bucket, object, None, Default::default(), &Default::default()).await {
|
||||
match (&*self.ecstore).get_object_reader(bucket, object, None, Default::default(), &Default::default()).await {
|
||||
Ok(mut reader) => {
|
||||
match reader.read_all().await {
|
||||
Ok(data) => Ok(Some(data)),
|
||||
@@ -140,7 +154,7 @@ impl HealStorageAPI for ECStoreHealStorage {
|
||||
debug!("Putting object data: {}/{} ({} bytes)", bucket, object, data.len());
|
||||
|
||||
let mut reader = rustfs_ecstore::store_api::PutObjReader::from_vec(data.to_vec());
|
||||
match self.ecstore.put_object(bucket, object, &mut reader, &Default::default()).await {
|
||||
match (&*self.ecstore).put_object(bucket, object, &mut reader, &Default::default()).await {
|
||||
Ok(_) => {
|
||||
info!("Successfully put object: {}/{}", bucket, object);
|
||||
Ok(())
|
||||
@@ -170,37 +184,79 @@ impl HealStorageAPI for ECStoreHealStorage {
|
||||
async fn verify_object_integrity(&self, bucket: &str, object: &str) -> Result<bool> {
|
||||
debug!("Verifying object integrity: {}/{}", bucket, object);
|
||||
|
||||
// TODO: 实现对象完整性检查
|
||||
// 1. 获取对象元数据
|
||||
// 2. 检查数据块完整性
|
||||
// 3. 验证校验和
|
||||
// 4. 检查 EC 编码正确性
|
||||
|
||||
// 临时实现:总是返回 true
|
||||
info!("Object integrity check passed: {}/{}", bucket, object);
|
||||
Ok(true)
|
||||
// Try to get object info and data to verify integrity
|
||||
match self.get_object_meta(bucket, object).await? {
|
||||
Some(obj_info) => {
|
||||
// Check if object has valid metadata
|
||||
if obj_info.size < 0 {
|
||||
warn!("Object has invalid size: {}/{}", bucket, object);
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
// Try to read object data to verify it's accessible
|
||||
match self.get_object_data(bucket, object).await {
|
||||
Ok(Some(_)) => {
|
||||
info!("Object integrity check passed: {}/{}", bucket, object);
|
||||
Ok(true)
|
||||
}
|
||||
Ok(None) => {
|
||||
warn!("Object data not found: {}/{}", bucket, object);
|
||||
Ok(false)
|
||||
}
|
||||
Err(_) => {
|
||||
warn!("Object data read failed: {}/{}", bucket, object);
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
warn!("Object metadata not found: {}/{}", bucket, object);
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn ec_decode_rebuild(&self, bucket: &str, object: &str) -> Result<Vec<u8>> {
|
||||
debug!("EC decode rebuild: {}/{}", bucket, object);
|
||||
|
||||
// TODO: 实现 EC 解码重建
|
||||
// 1. 获取对象元数据
|
||||
// 2. 读取可用的数据块
|
||||
// 3. 使用 EC 算法重建缺失数据
|
||||
// 4. 返回完整数据
|
||||
// Use ecstore's heal_object to rebuild the object
|
||||
let heal_opts = HealOpts {
|
||||
recursive: false,
|
||||
dry_run: false,
|
||||
remove: false,
|
||||
recreate: true,
|
||||
scan_mode: HEAL_DEEP_SCAN,
|
||||
update_parity: true,
|
||||
no_lock: false,
|
||||
pool: None,
|
||||
set: None,
|
||||
};
|
||||
|
||||
// 临时实现:尝试获取对象数据
|
||||
match self.get_object_data(bucket, object).await? {
|
||||
Some(data) => {
|
||||
info!("EC decode rebuild successful: {}/{}", bucket, object);
|
||||
Ok(data)
|
||||
match self.heal_object(bucket, object, None, &heal_opts).await {
|
||||
Ok((_result, error)) => {
|
||||
if error.is_some() {
|
||||
return Err(Error::TaskExecutionFailed {
|
||||
message: format!("Heal failed: {:?}", error),
|
||||
});
|
||||
}
|
||||
|
||||
// After healing, try to read the object data
|
||||
match self.get_object_data(bucket, object).await? {
|
||||
Some(data) => {
|
||||
info!("EC decode rebuild successful: {}/{} ({} bytes)", bucket, object, data.len());
|
||||
Ok(data)
|
||||
}
|
||||
None => {
|
||||
error!("Object not found after heal: {}/{}", bucket, object);
|
||||
Err(Error::TaskExecutionFailed {
|
||||
message: format!("Object not found after heal: {}/{}", bucket, object),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
error!("Object not found for EC decode: {}/{}", bucket, object);
|
||||
Err(Error::TaskExecutionFailed {
|
||||
message: format!("Object not found: {}/{}", bucket, object),
|
||||
})
|
||||
Err(e) => {
|
||||
error!("Heal operation failed: {}/{} - {}", bucket, object, e);
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -208,13 +264,8 @@ impl HealStorageAPI for ECStoreHealStorage {
|
||||
async fn get_disk_status(&self, endpoint: &Endpoint) -> Result<DiskStatus> {
|
||||
debug!("Getting disk status: {:?}", endpoint);
|
||||
|
||||
// TODO: 实现磁盘状态检查
|
||||
// 1. 检查磁盘是否可访问
|
||||
// 2. 检查磁盘格式
|
||||
// 3. 检查权限
|
||||
// 4. 返回状态
|
||||
|
||||
// 临时实现:总是返回 Ok
|
||||
// TODO: implement disk status check using ecstore
|
||||
// For now, return Ok status
|
||||
info!("Disk status check: {:?} - OK", endpoint);
|
||||
Ok(DiskStatus::Ok)
|
||||
}
|
||||
@@ -222,14 +273,20 @@ impl HealStorageAPI for ECStoreHealStorage {
|
||||
async fn format_disk(&self, endpoint: &Endpoint) -> Result<()> {
|
||||
debug!("Formatting disk: {:?}", endpoint);
|
||||
|
||||
// TODO: 实现磁盘格式化
|
||||
// 1. 检查磁盘权限
|
||||
// 2. 执行格式化操作
|
||||
// 3. 验证格式化结果
|
||||
|
||||
// 临时实现:总是成功
|
||||
info!("Disk formatted successfully: {:?}", endpoint);
|
||||
Ok(())
|
||||
// Use ecstore's heal_format
|
||||
match self.heal_format(false).await {
|
||||
Ok((_, error)) => {
|
||||
if error.is_some() {
|
||||
return Err(Error::other(format!("Format failed: {:?}", error)));
|
||||
}
|
||||
info!("Successfully formatted disk: {:?}", endpoint);
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to format disk: {:?} - {}", endpoint, e);
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_bucket_info(&self, bucket: &str) -> Result<Option<BucketInfo>> {
|
||||
@@ -247,14 +304,28 @@ impl HealStorageAPI for ECStoreHealStorage {
|
||||
async fn heal_bucket_metadata(&self, bucket: &str) -> Result<()> {
|
||||
debug!("Healing bucket metadata: {}", bucket);
|
||||
|
||||
// TODO: 实现桶元数据修复
|
||||
// 1. 检查桶元数据完整性
|
||||
// 2. 修复损坏的元数据
|
||||
// 3. 更新桶配置
|
||||
let heal_opts = HealOpts {
|
||||
recursive: true,
|
||||
dry_run: false,
|
||||
remove: false,
|
||||
recreate: false,
|
||||
scan_mode: HEAL_NORMAL_SCAN,
|
||||
update_parity: false,
|
||||
no_lock: false,
|
||||
pool: None,
|
||||
set: None,
|
||||
};
|
||||
|
||||
// 临时实现:总是成功
|
||||
info!("Bucket metadata healed successfully: {}", bucket);
|
||||
Ok(())
|
||||
match self.heal_bucket(bucket, &heal_opts).await {
|
||||
Ok(_) => {
|
||||
info!("Successfully healed bucket metadata: {}", bucket);
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to heal bucket metadata: {} - {}", bucket, e);
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn list_buckets(&self) -> Result<Vec<BucketInfo>> {
|
||||
@@ -270,36 +341,106 @@ impl HealStorageAPI for ECStoreHealStorage {
|
||||
}
|
||||
|
||||
async fn object_exists(&self, bucket: &str, object: &str) -> Result<bool> {
|
||||
debug!("Checking if object exists: {}/{}", bucket, object);
|
||||
debug!("Checking object exists: {}/{}", bucket, object);
|
||||
|
||||
match self.ecstore.get_object_info(bucket, object, &Default::default()).await {
|
||||
Ok(_) => Ok(true),
|
||||
Err(e) => {
|
||||
error!("Failed to check object existence: {}/{} - {}", bucket, object, e);
|
||||
Err(Error::other(e))
|
||||
}
|
||||
match self.get_object_meta(bucket, object).await {
|
||||
Ok(Some(_)) => Ok(true),
|
||||
Ok(None) => Ok(false),
|
||||
Err(_) => Ok(false),
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_object_size(&self, bucket: &str, object: &str) -> Result<Option<u64>> {
|
||||
debug!("Getting object size: {}/{}", bucket, object);
|
||||
|
||||
match self.ecstore.get_object_info(bucket, object, &Default::default()).await {
|
||||
Ok(info) => Ok(Some(info.size as u64)),
|
||||
Err(e) => {
|
||||
error!("Failed to get object size: {}/{} - {}", bucket, object, e);
|
||||
Err(Error::other(e))
|
||||
}
|
||||
match self.get_object_meta(bucket, object).await {
|
||||
Ok(Some(obj_info)) => Ok(Some(obj_info.size as u64)),
|
||||
Ok(None) => Ok(None),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_object_checksum(&self, bucket: &str, object: &str) -> Result<Option<String>> {
|
||||
debug!("Getting object checksum: {}/{}", bucket, object);
|
||||
|
||||
match self.ecstore.get_object_info(bucket, object, &Default::default()).await {
|
||||
Ok(info) => Ok(info.etag),
|
||||
match self.get_object_meta(bucket, object).await {
|
||||
Ok(Some(obj_info)) => {
|
||||
// Convert checksum bytes to hex string
|
||||
let checksum = obj_info.checksum.iter()
|
||||
.map(|b| format!("{:02x}", b))
|
||||
.collect::<String>();
|
||||
Ok(Some(checksum))
|
||||
}
|
||||
Ok(None) => Ok(None),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
}
|
||||
|
||||
async fn heal_object(&self, bucket: &str, object: &str, version_id: Option<&str>, opts: &HealOpts) -> Result<(HealResultItem, Option<Error>)> {
|
||||
debug!("Healing object: {}/{}", bucket, object);
|
||||
|
||||
let version_id_str = version_id.unwrap_or("");
|
||||
|
||||
match self.ecstore.heal_object(bucket, object, version_id_str, opts).await {
|
||||
Ok((result, ecstore_error)) => {
|
||||
let error = ecstore_error.map(|e| Error::other(e));
|
||||
info!("Heal object completed: {}/{} - result: {:?}, error: {:?}", bucket, object, result, error);
|
||||
Ok((result, error))
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to get object checksum: {}/{} - {}", bucket, object, e);
|
||||
error!("Heal object failed: {}/{} - {}", bucket, object, e);
|
||||
Err(Error::other(e))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn heal_bucket(&self, bucket: &str, opts: &HealOpts) -> Result<HealResultItem> {
|
||||
debug!("Healing bucket: {}", bucket);
|
||||
|
||||
match self.ecstore.heal_bucket(bucket, opts).await {
|
||||
Ok(result) => {
|
||||
info!("Heal bucket completed: {} - result: {:?}", bucket, result);
|
||||
Ok(result)
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Heal bucket failed: {} - {}", bucket, e);
|
||||
Err(Error::other(e))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn heal_format(&self, dry_run: bool) -> Result<(HealResultItem, Option<Error>)> {
|
||||
debug!("Healing format (dry_run: {})", dry_run);
|
||||
|
||||
match self.ecstore.heal_format(dry_run).await {
|
||||
Ok((result, ecstore_error)) => {
|
||||
let error = ecstore_error.map(|e| Error::other(e));
|
||||
info!("Heal format completed - result: {:?}, error: {:?}", result, error);
|
||||
Ok((result, error))
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Heal format failed: {}", e);
|
||||
Err(Error::other(e))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn list_objects_for_heal(&self, bucket: &str, prefix: &str) -> Result<Vec<String>> {
|
||||
debug!("Listing objects for heal: {}/{}", bucket, prefix);
|
||||
|
||||
// Use list_objects_v2 to get objects
|
||||
match self.ecstore.clone().list_objects_v2(
|
||||
bucket, prefix, None, None, 1000, false, None
|
||||
).await {
|
||||
Ok(list_info) => {
|
||||
let objects: Vec<String> = list_info.objects.into_iter()
|
||||
.map(|obj| obj.name)
|
||||
.collect();
|
||||
info!("Found {} objects for heal in {}/{}", objects.len(), bucket, prefix);
|
||||
Ok(objects)
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to list objects for heal: {}/{} - {}", bucket, prefix, e);
|
||||
Err(Error::other(e))
|
||||
}
|
||||
}
|
||||
|
||||
+536
-91
@@ -12,43 +12,44 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::error::Result;
|
||||
use crate::error::{Error, Result};
|
||||
use crate::heal::{progress::HealProgress, storage::HealStorageAPI};
|
||||
use rustfs_ecstore::disk::endpoint::Endpoint;
|
||||
use crate::heal::storage::DiskStatus;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
sync::Arc,
|
||||
time::{Duration, SystemTime},
|
||||
};
|
||||
use tokio::sync::RwLock;
|
||||
use tracing::{debug, error, info};
|
||||
use tracing::{error, info, warn};
|
||||
use uuid::Uuid;
|
||||
|
||||
/// Heal 扫描模式
|
||||
/// Heal scan mode
|
||||
pub type HealScanMode = usize;
|
||||
|
||||
pub const HEAL_UNKNOWN_SCAN: HealScanMode = 0;
|
||||
pub const HEAL_NORMAL_SCAN: HealScanMode = 1;
|
||||
pub const HEAL_DEEP_SCAN: HealScanMode = 2;
|
||||
|
||||
/// Heal 类型
|
||||
/// Heal type
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum HealType {
|
||||
/// 对象 heal
|
||||
/// Object heal
|
||||
Object {
|
||||
bucket: String,
|
||||
object: String,
|
||||
version_id: Option<String>,
|
||||
},
|
||||
/// 桶 heal
|
||||
/// Bucket heal
|
||||
Bucket {
|
||||
bucket: String,
|
||||
},
|
||||
/// 磁盘 heal
|
||||
/// Disk heal
|
||||
Disk {
|
||||
endpoint: Endpoint,
|
||||
},
|
||||
/// 元数据 heal
|
||||
/// Metadata heal
|
||||
Metadata {
|
||||
bucket: String,
|
||||
object: String,
|
||||
@@ -57,7 +58,7 @@ pub enum HealType {
|
||||
MRF {
|
||||
meta_path: String,
|
||||
},
|
||||
/// EC 解码 heal
|
||||
/// EC decode heal
|
||||
ECDecode {
|
||||
bucket: String,
|
||||
object: String,
|
||||
@@ -65,16 +66,16 @@ pub enum HealType {
|
||||
},
|
||||
}
|
||||
|
||||
/// Heal 优先级
|
||||
/// Heal priority
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
pub enum HealPriority {
|
||||
/// 低优先级
|
||||
/// Low priority
|
||||
Low = 0,
|
||||
/// 普通优先级
|
||||
/// Normal priority
|
||||
Normal = 1,
|
||||
/// 高优先级
|
||||
/// High priority
|
||||
High = 2,
|
||||
/// 紧急优先级
|
||||
/// Urgent priority
|
||||
Urgent = 3,
|
||||
}
|
||||
|
||||
@@ -84,22 +85,22 @@ impl Default for HealPriority {
|
||||
}
|
||||
}
|
||||
|
||||
/// Heal 选项
|
||||
/// Heal options
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct HealOptions {
|
||||
/// 扫描模式
|
||||
/// Scan mode
|
||||
pub scan_mode: HealScanMode,
|
||||
/// 是否删除损坏数据
|
||||
/// Whether to remove corrupted data
|
||||
pub remove_corrupted: bool,
|
||||
/// 是否重新创建
|
||||
/// Whether to recreate
|
||||
pub recreate_missing: bool,
|
||||
/// 是否更新奇偶校验
|
||||
/// Whether to update parity
|
||||
pub update_parity: bool,
|
||||
/// 是否递归处理
|
||||
/// Whether to recursively process
|
||||
pub recursive: bool,
|
||||
/// 是否试运行
|
||||
/// Whether to dry run
|
||||
pub dry_run: bool,
|
||||
/// 超时时间
|
||||
/// Timeout
|
||||
pub timeout: Option<Duration>,
|
||||
}
|
||||
|
||||
@@ -112,40 +113,40 @@ impl Default for HealOptions {
|
||||
update_parity: true,
|
||||
recursive: false,
|
||||
dry_run: false,
|
||||
timeout: Some(Duration::from_secs(300)), // 5分钟默认超时
|
||||
timeout: Some(Duration::from_secs(300)), // 5 minutes default timeout
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Heal 任务状态
|
||||
/// Heal task status
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum HealTaskStatus {
|
||||
/// 等待中
|
||||
/// Pending
|
||||
Pending,
|
||||
/// 运行中
|
||||
/// Running
|
||||
Running,
|
||||
/// 完成
|
||||
/// Completed
|
||||
Completed,
|
||||
/// 失败
|
||||
/// Failed
|
||||
Failed { error: String },
|
||||
/// 取消
|
||||
/// Cancelled
|
||||
Cancelled,
|
||||
/// 超时
|
||||
/// Timeout
|
||||
Timeout,
|
||||
}
|
||||
|
||||
/// Heal 请求
|
||||
/// Heal request
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct HealRequest {
|
||||
/// 请求 ID
|
||||
/// Request ID
|
||||
pub id: String,
|
||||
/// Heal 类型
|
||||
/// Heal type
|
||||
pub heal_type: HealType,
|
||||
/// Heal 选项
|
||||
/// Heal options
|
||||
pub options: HealOptions,
|
||||
/// 优先级
|
||||
/// Priority
|
||||
pub priority: HealPriority,
|
||||
/// 创建时间
|
||||
/// Created time
|
||||
pub created_at: SystemTime,
|
||||
}
|
||||
|
||||
@@ -209,27 +210,27 @@ impl HealRequest {
|
||||
}
|
||||
}
|
||||
|
||||
/// Heal 任务
|
||||
/// Heal task
|
||||
pub struct HealTask {
|
||||
/// 任务 ID
|
||||
/// Task ID
|
||||
pub id: String,
|
||||
/// Heal 类型
|
||||
/// Heal type
|
||||
pub heal_type: HealType,
|
||||
/// Heal 选项
|
||||
/// Heal options
|
||||
pub options: HealOptions,
|
||||
/// 任务状态
|
||||
/// Task status
|
||||
pub status: Arc<RwLock<HealTaskStatus>>,
|
||||
/// 进度跟踪
|
||||
/// Progress tracking
|
||||
pub progress: Arc<RwLock<HealProgress>>,
|
||||
/// 创建时间
|
||||
/// Created time
|
||||
pub created_at: SystemTime,
|
||||
/// 开始时间
|
||||
/// Started time
|
||||
pub started_at: Arc<RwLock<Option<SystemTime>>>,
|
||||
/// 完成时间
|
||||
/// Completed time
|
||||
pub completed_at: Arc<RwLock<Option<SystemTime>>>,
|
||||
/// 取消令牌
|
||||
/// Cancel token
|
||||
pub cancel_token: tokio_util::sync::CancellationToken,
|
||||
/// 存储层接口
|
||||
/// Storage layer interface
|
||||
pub storage: Arc<dyn HealStorageAPI>,
|
||||
}
|
||||
|
||||
@@ -250,7 +251,7 @@ impl HealTask {
|
||||
}
|
||||
|
||||
pub async fn execute(&self) -> Result<()> {
|
||||
// 更新状态为运行中
|
||||
// update status to running
|
||||
{
|
||||
let mut status = self.status.write().await;
|
||||
*status = HealTaskStatus::Running;
|
||||
@@ -283,7 +284,7 @@ impl HealTask {
|
||||
}
|
||||
};
|
||||
|
||||
// 更新完成时间和状态
|
||||
// update completed time and status
|
||||
{
|
||||
let mut completed_at = self.completed_at.write().await;
|
||||
*completed_at = Some(SystemTime::now());
|
||||
@@ -323,83 +324,527 @@ impl HealTask {
|
||||
self.progress.read().await.clone()
|
||||
}
|
||||
|
||||
// 具体的 heal 实现方法
|
||||
async fn heal_object(&self, bucket: &str, object: &str, _version_id: Option<&str>) -> Result<()> {
|
||||
debug!("Healing object: {}/{}", bucket, object);
|
||||
// specific heal implementation method
|
||||
async fn heal_object(&self, bucket: &str, object: &str, version_id: Option<&str>) -> Result<()> {
|
||||
info!("Healing object: {}/{}", bucket, object);
|
||||
|
||||
// 更新进度
|
||||
// update progress
|
||||
{
|
||||
let mut progress = self.progress.write().await;
|
||||
progress.set_current_object(Some(format!("{}/{}", bucket, object)));
|
||||
progress.update_progress(0, 4, 0, 0); // 开始heal,总共4个步骤
|
||||
}
|
||||
|
||||
// TODO: 实现具体的对象 heal 逻辑
|
||||
// 1. 检查对象完整性
|
||||
// 2. 如果损坏,尝试 EC 重建
|
||||
// 3. 更新对象数据
|
||||
// 4. 更新进度
|
||||
// Step 1: Check if object exists and get metadata
|
||||
info!("Step 1: Checking object existence and metadata");
|
||||
let object_exists = self.storage.object_exists(bucket, object).await?;
|
||||
if !object_exists {
|
||||
warn!("Object does not exist: {}/{}", bucket, object);
|
||||
if self.options.recreate_missing {
|
||||
info!("Attempting to recreate missing object: {}/{}", bucket, object);
|
||||
return self.recreate_missing_object(bucket, object, version_id).await;
|
||||
} else {
|
||||
return Err(Error::TaskExecutionFailed {
|
||||
message: format!("Object not found: {}/{}", bucket, object),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
let mut progress = self.progress.write().await;
|
||||
progress.update_progress(1, 1, 0, 1024); // 示例数据
|
||||
progress.update_progress(1, 4, 0, 0);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
// Step 2: Verify object integrity
|
||||
info!("Step 2: Verifying object integrity");
|
||||
let integrity_ok = self.storage.verify_object_integrity(bucket, object).await?;
|
||||
if integrity_ok {
|
||||
info!("Object integrity check passed: {}/{}", bucket, object);
|
||||
{
|
||||
let mut progress = self.progress.write().await;
|
||||
progress.update_progress(4, 4, 0, 0);
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
warn!("Object integrity check failed: {}/{}", bucket, object);
|
||||
{
|
||||
let mut progress = self.progress.write().await;
|
||||
progress.update_progress(2, 4, 0, 0);
|
||||
}
|
||||
|
||||
// Step 3: Perform actual heal using ecstore
|
||||
info!("Step 3: Performing heal using ecstore");
|
||||
let heal_opts = rustfs_ecstore::heal::heal_commands::HealOpts {
|
||||
recursive: self.options.recursive,
|
||||
dry_run: self.options.dry_run,
|
||||
remove: self.options.remove_corrupted,
|
||||
recreate: self.options.recreate_missing,
|
||||
scan_mode: match self.options.scan_mode {
|
||||
crate::heal::task::HEAL_UNKNOWN_SCAN => rustfs_ecstore::heal::heal_commands::HEAL_UNKNOWN_SCAN,
|
||||
crate::heal::task::HEAL_NORMAL_SCAN => rustfs_ecstore::heal::heal_commands::HEAL_NORMAL_SCAN,
|
||||
crate::heal::task::HEAL_DEEP_SCAN => rustfs_ecstore::heal::heal_commands::HEAL_DEEP_SCAN,
|
||||
_ => rustfs_ecstore::heal::heal_commands::HEAL_NORMAL_SCAN,
|
||||
},
|
||||
update_parity: self.options.update_parity,
|
||||
no_lock: false,
|
||||
pool: None,
|
||||
set: None,
|
||||
};
|
||||
|
||||
match self.storage.heal_object(bucket, object, version_id, &heal_opts).await {
|
||||
Ok((result, error)) => {
|
||||
if let Some(e) = error {
|
||||
error!("Heal operation failed: {}/{} - {}", bucket, object, e);
|
||||
|
||||
// If heal failed and remove_corrupted is enabled, delete the corrupted object
|
||||
if self.options.remove_corrupted {
|
||||
warn!("Removing corrupted object: {}/{}", bucket, object);
|
||||
if !self.options.dry_run {
|
||||
self.storage.delete_object(bucket, object).await?;
|
||||
info!("Successfully deleted corrupted object: {}/{}", bucket, object);
|
||||
} else {
|
||||
info!("Dry run mode - would delete corrupted object: {}/{}", bucket, object);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
let mut progress = self.progress.write().await;
|
||||
progress.update_progress(4, 4, 0, 0);
|
||||
}
|
||||
|
||||
return Err(Error::TaskExecutionFailed {
|
||||
message: format!("Failed to heal object {}/{}: {}", bucket, object, e),
|
||||
});
|
||||
}
|
||||
|
||||
// Step 4: Verify heal result
|
||||
info!("Step 4: Verifying heal result");
|
||||
let object_size = result.object_size as u64;
|
||||
info!("Heal completed successfully: {}/{} ({} bytes, {} drives healed)",
|
||||
bucket, object, object_size, result.after.drives.len());
|
||||
|
||||
{
|
||||
let mut progress = self.progress.write().await;
|
||||
progress.update_progress(4, 4, object_size, object_size);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Heal operation failed: {}/{} - {}", bucket, object, e);
|
||||
|
||||
// If heal failed and remove_corrupted is enabled, delete the corrupted object
|
||||
if self.options.remove_corrupted {
|
||||
warn!("Removing corrupted object: {}/{}", bucket, object);
|
||||
if !self.options.dry_run {
|
||||
self.storage.delete_object(bucket, object).await?;
|
||||
info!("Successfully deleted corrupted object: {}/{}", bucket, object);
|
||||
} else {
|
||||
info!("Dry run mode - would delete corrupted object: {}/{}", bucket, object);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
let mut progress = self.progress.write().await;
|
||||
progress.update_progress(4, 4, 0, 0);
|
||||
}
|
||||
|
||||
Err(Error::TaskExecutionFailed {
|
||||
message: format!("Failed to heal object {}/{}: {}", bucket, object, e),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Recreate missing object (for EC decode scenarios)
|
||||
async fn recreate_missing_object(&self, bucket: &str, object: &str, version_id: Option<&str>) -> Result<()> {
|
||||
info!("Attempting to recreate missing object: {}/{}", bucket, object);
|
||||
|
||||
// Use ecstore's heal_object with recreate option
|
||||
let heal_opts = rustfs_ecstore::heal::heal_commands::HealOpts {
|
||||
recursive: false,
|
||||
dry_run: self.options.dry_run,
|
||||
remove: false,
|
||||
recreate: true,
|
||||
scan_mode: rustfs_ecstore::heal::heal_commands::HEAL_DEEP_SCAN,
|
||||
update_parity: true,
|
||||
no_lock: false,
|
||||
pool: None,
|
||||
set: None,
|
||||
};
|
||||
|
||||
match self.storage.heal_object(bucket, object, version_id, &heal_opts).await {
|
||||
Ok((result, error)) => {
|
||||
if let Some(e) = error {
|
||||
error!("Failed to recreate missing object: {}/{} - {}", bucket, object, e);
|
||||
return Err(Error::TaskExecutionFailed {
|
||||
message: format!("Failed to recreate missing object {}/{}: {}", bucket, object, e),
|
||||
});
|
||||
}
|
||||
|
||||
let object_size = result.object_size as u64;
|
||||
info!("Successfully recreated missing object: {}/{} ({} bytes)", bucket, object, object_size);
|
||||
|
||||
{
|
||||
let mut progress = self.progress.write().await;
|
||||
progress.update_progress(4, 4, object_size, object_size);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to recreate missing object: {}/{} - {}", bucket, object, e);
|
||||
Err(Error::TaskExecutionFailed {
|
||||
message: format!("Failed to recreate missing object {}/{}: {}", bucket, object, e),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn heal_bucket(&self, bucket: &str) -> Result<()> {
|
||||
debug!("Healing bucket: {}", bucket);
|
||||
info!("Healing bucket: {}", bucket);
|
||||
|
||||
// TODO: 实现桶 heal 逻辑
|
||||
// 1. 检查桶元数据
|
||||
// 2. 修复桶配置
|
||||
// 3. 更新进度
|
||||
// update progress
|
||||
{
|
||||
let mut progress = self.progress.write().await;
|
||||
progress.set_current_object(Some(format!("bucket: {}", bucket)));
|
||||
progress.update_progress(0, 3, 0, 0);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
// Step 1: Check if bucket exists
|
||||
info!("Step 1: Checking bucket existence");
|
||||
let bucket_exists = self.storage.get_bucket_info(bucket).await?.is_some();
|
||||
if !bucket_exists {
|
||||
warn!("Bucket does not exist: {}", bucket);
|
||||
return Err(Error::TaskExecutionFailed {
|
||||
message: format!("Bucket not found: {}", bucket),
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
let mut progress = self.progress.write().await;
|
||||
progress.update_progress(1, 3, 0, 0);
|
||||
}
|
||||
|
||||
// Step 2: Perform bucket heal using ecstore
|
||||
info!("Step 2: Performing bucket heal using ecstore");
|
||||
let heal_opts = rustfs_ecstore::heal::heal_commands::HealOpts {
|
||||
recursive: self.options.recursive,
|
||||
dry_run: self.options.dry_run,
|
||||
remove: self.options.remove_corrupted,
|
||||
recreate: self.options.recreate_missing,
|
||||
scan_mode: match self.options.scan_mode {
|
||||
crate::heal::task::HEAL_UNKNOWN_SCAN => rustfs_ecstore::heal::heal_commands::HEAL_UNKNOWN_SCAN,
|
||||
crate::heal::task::HEAL_NORMAL_SCAN => rustfs_ecstore::heal::heal_commands::HEAL_NORMAL_SCAN,
|
||||
crate::heal::task::HEAL_DEEP_SCAN => rustfs_ecstore::heal::heal_commands::HEAL_DEEP_SCAN,
|
||||
_ => rustfs_ecstore::heal::heal_commands::HEAL_NORMAL_SCAN,
|
||||
},
|
||||
update_parity: self.options.update_parity,
|
||||
no_lock: false,
|
||||
pool: None,
|
||||
set: None,
|
||||
};
|
||||
|
||||
match self.storage.heal_bucket(bucket, &heal_opts).await {
|
||||
Ok(result) => {
|
||||
info!("Bucket heal completed successfully: {} ({} drives)", bucket, result.after.drives.len());
|
||||
|
||||
{
|
||||
let mut progress = self.progress.write().await;
|
||||
progress.update_progress(3, 3, 0, 0);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Bucket heal failed: {} - {}", bucket, e);
|
||||
{
|
||||
let mut progress = self.progress.write().await;
|
||||
progress.update_progress(3, 3, 0, 0);
|
||||
}
|
||||
Err(Error::TaskExecutionFailed {
|
||||
message: format!("Failed to heal bucket {}: {}", bucket, e),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn heal_disk(&self, endpoint: &Endpoint) -> Result<()> {
|
||||
debug!("Healing disk: {:?}", endpoint);
|
||||
info!("Healing disk: {:?}", endpoint);
|
||||
|
||||
// TODO: 实现磁盘 heal 逻辑
|
||||
// 1. 检查磁盘状态
|
||||
// 2. 格式化磁盘(如果需要)
|
||||
// 3. 更新进度
|
||||
// update progress
|
||||
{
|
||||
let mut progress = self.progress.write().await;
|
||||
progress.set_current_object(Some(format!("disk: {:?}", endpoint)));
|
||||
progress.update_progress(0, 3, 0, 0);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
// Step 1: Check disk status
|
||||
info!("Step 1: Checking disk status");
|
||||
let disk_status = self.storage.get_disk_status(endpoint).await?;
|
||||
if disk_status == DiskStatus::Ok {
|
||||
info!("Disk is already healthy: {:?}", endpoint);
|
||||
{
|
||||
let mut progress = self.progress.write().await;
|
||||
progress.update_progress(3, 3, 0, 0);
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
{
|
||||
let mut progress = self.progress.write().await;
|
||||
progress.update_progress(1, 3, 0, 0);
|
||||
}
|
||||
|
||||
// Step 2: Perform disk heal using ecstore
|
||||
info!("Step 2: Performing disk heal using ecstore");
|
||||
match self.storage.heal_format(self.options.dry_run).await {
|
||||
Ok((result, error)) => {
|
||||
if let Some(e) = error {
|
||||
error!("Disk heal failed: {:?} - {}", endpoint, e);
|
||||
{
|
||||
let mut progress = self.progress.write().await;
|
||||
progress.update_progress(3, 3, 0, 0);
|
||||
}
|
||||
return Err(Error::TaskExecutionFailed {
|
||||
message: format!("Failed to heal disk {:?}: {}", endpoint, e),
|
||||
});
|
||||
}
|
||||
|
||||
info!("Disk heal completed successfully: {:?} ({} drives)", endpoint, result.after.drives.len());
|
||||
|
||||
{
|
||||
let mut progress = self.progress.write().await;
|
||||
progress.update_progress(3, 3, 0, 0);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Disk heal failed: {:?} - {}", endpoint, e);
|
||||
{
|
||||
let mut progress = self.progress.write().await;
|
||||
progress.update_progress(3, 3, 0, 0);
|
||||
}
|
||||
Err(Error::TaskExecutionFailed {
|
||||
message: format!("Failed to heal disk {:?}: {}", endpoint, e),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn heal_metadata(&self, bucket: &str, object: &str) -> Result<()> {
|
||||
debug!("Healing metadata: {}/{}", bucket, object);
|
||||
info!("Healing metadata: {}/{}", bucket, object);
|
||||
|
||||
// TODO: 实现元数据 heal 逻辑
|
||||
// 1. 检查元数据完整性
|
||||
// 2. 重建元数据
|
||||
// 3. 更新进度
|
||||
// update progress
|
||||
{
|
||||
let mut progress = self.progress.write().await;
|
||||
progress.set_current_object(Some(format!("metadata: {}/{}", bucket, object)));
|
||||
progress.update_progress(0, 3, 0, 0);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
// Step 1: Check if object exists
|
||||
info!("Step 1: Checking object existence");
|
||||
let object_exists = self.storage.object_exists(bucket, object).await?;
|
||||
if !object_exists {
|
||||
warn!("Object does not exist: {}/{}", bucket, object);
|
||||
return Err(Error::TaskExecutionFailed {
|
||||
message: format!("Object not found: {}/{}", bucket, object),
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
let mut progress = self.progress.write().await;
|
||||
progress.update_progress(1, 3, 0, 0);
|
||||
}
|
||||
|
||||
// Step 2: Perform metadata heal using ecstore
|
||||
info!("Step 2: Performing metadata heal using ecstore");
|
||||
let heal_opts = rustfs_ecstore::heal::heal_commands::HealOpts {
|
||||
recursive: false,
|
||||
dry_run: self.options.dry_run,
|
||||
remove: false,
|
||||
recreate: false,
|
||||
scan_mode: rustfs_ecstore::heal::heal_commands::HEAL_DEEP_SCAN,
|
||||
update_parity: false,
|
||||
no_lock: false,
|
||||
pool: None,
|
||||
set: None,
|
||||
};
|
||||
|
||||
match self.storage.heal_object(bucket, object, None, &heal_opts).await {
|
||||
Ok((result, error)) => {
|
||||
if let Some(e) = error {
|
||||
error!("Metadata heal failed: {}/{} - {}", bucket, object, e);
|
||||
{
|
||||
let mut progress = self.progress.write().await;
|
||||
progress.update_progress(3, 3, 0, 0);
|
||||
}
|
||||
return Err(Error::TaskExecutionFailed {
|
||||
message: format!("Failed to heal metadata {}/{}: {}", bucket, object, e),
|
||||
});
|
||||
}
|
||||
|
||||
info!("Metadata heal completed successfully: {}/{} ({} drives)", bucket, object, result.after.drives.len());
|
||||
|
||||
{
|
||||
let mut progress = self.progress.write().await;
|
||||
progress.update_progress(3, 3, 0, 0);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Metadata heal failed: {}/{} - {}", bucket, object, e);
|
||||
{
|
||||
let mut progress = self.progress.write().await;
|
||||
progress.update_progress(3, 3, 0, 0);
|
||||
}
|
||||
Err(Error::TaskExecutionFailed {
|
||||
message: format!("Failed to heal metadata {}/{}: {}", bucket, object, e),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn heal_mrf(&self, meta_path: &str) -> Result<()> {
|
||||
debug!("Healing MRF: {}", meta_path);
|
||||
info!("Healing MRF: {}", meta_path);
|
||||
|
||||
// TODO: 实现 MRF heal 逻辑
|
||||
// 1. 检查元数据复制因子
|
||||
// 2. 修复元数据
|
||||
// 3. 更新进度
|
||||
// update progress
|
||||
{
|
||||
let mut progress = self.progress.write().await;
|
||||
progress.set_current_object(Some(format!("mrf: {}", meta_path)));
|
||||
progress.update_progress(0, 2, 0, 0);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
// Parse meta_path to extract bucket and object
|
||||
let parts: Vec<&str> = meta_path.split('/').collect();
|
||||
if parts.len() < 2 {
|
||||
return Err(Error::TaskExecutionFailed {
|
||||
message: format!("Invalid meta path format: {}", meta_path),
|
||||
});
|
||||
}
|
||||
|
||||
let bucket = parts[0];
|
||||
let object = parts[1..].join("/");
|
||||
|
||||
// Step 1: Perform MRF heal using ecstore
|
||||
info!("Step 1: Performing MRF heal using ecstore");
|
||||
let heal_opts = rustfs_ecstore::heal::heal_commands::HealOpts {
|
||||
recursive: true,
|
||||
dry_run: self.options.dry_run,
|
||||
remove: self.options.remove_corrupted,
|
||||
recreate: self.options.recreate_missing,
|
||||
scan_mode: rustfs_ecstore::heal::heal_commands::HEAL_DEEP_SCAN,
|
||||
update_parity: true,
|
||||
no_lock: false,
|
||||
pool: None,
|
||||
set: None,
|
||||
};
|
||||
|
||||
match self.storage.heal_object(bucket, &object, None, &heal_opts).await {
|
||||
Ok((result, error)) => {
|
||||
if let Some(e) = error {
|
||||
error!("MRF heal failed: {} - {}", meta_path, e);
|
||||
{
|
||||
let mut progress = self.progress.write().await;
|
||||
progress.update_progress(2, 2, 0, 0);
|
||||
}
|
||||
return Err(Error::TaskExecutionFailed {
|
||||
message: format!("Failed to heal MRF {}: {}", meta_path, e),
|
||||
});
|
||||
}
|
||||
|
||||
info!("MRF heal completed successfully: {} ({} drives)", meta_path, result.after.drives.len());
|
||||
|
||||
{
|
||||
let mut progress = self.progress.write().await;
|
||||
progress.update_progress(2, 2, 0, 0);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
error!("MRF heal failed: {} - {}", meta_path, e);
|
||||
{
|
||||
let mut progress = self.progress.write().await;
|
||||
progress.update_progress(2, 2, 0, 0);
|
||||
}
|
||||
Err(Error::TaskExecutionFailed {
|
||||
message: format!("Failed to heal MRF {}: {}", meta_path, e),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn heal_ec_decode(&self, bucket: &str, object: &str, _version_id: Option<&str>) -> Result<()> {
|
||||
debug!("Healing EC decode: {}/{}", bucket, object);
|
||||
async fn heal_ec_decode(&self, bucket: &str, object: &str, version_id: Option<&str>) -> Result<()> {
|
||||
info!("Healing EC decode: {}/{}", bucket, object);
|
||||
|
||||
// TODO: 实现 EC 解码 heal 逻辑
|
||||
// 1. 检查 EC 分片
|
||||
// 2. 使用 EC 算法重建数据
|
||||
// 3. 更新进度
|
||||
// update progress
|
||||
{
|
||||
let mut progress = self.progress.write().await;
|
||||
progress.set_current_object(Some(format!("ec_decode: {}/{}", bucket, object)));
|
||||
progress.update_progress(0, 3, 0, 0);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
// Step 1: Check if object exists
|
||||
info!("Step 1: Checking object existence");
|
||||
let object_exists = self.storage.object_exists(bucket, object).await?;
|
||||
if !object_exists {
|
||||
warn!("Object does not exist: {}/{}", bucket, object);
|
||||
return Err(Error::TaskExecutionFailed {
|
||||
message: format!("Object not found: {}/{}", bucket, object),
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
let mut progress = self.progress.write().await;
|
||||
progress.update_progress(1, 3, 0, 0);
|
||||
}
|
||||
|
||||
// Step 2: Perform EC decode heal using ecstore
|
||||
info!("Step 2: Performing EC decode heal using ecstore");
|
||||
let heal_opts = rustfs_ecstore::heal::heal_commands::HealOpts {
|
||||
recursive: false,
|
||||
dry_run: self.options.dry_run,
|
||||
remove: false,
|
||||
recreate: true,
|
||||
scan_mode: rustfs_ecstore::heal::heal_commands::HEAL_DEEP_SCAN,
|
||||
update_parity: true,
|
||||
no_lock: false,
|
||||
pool: None,
|
||||
set: None,
|
||||
};
|
||||
|
||||
match self.storage.heal_object(bucket, object, version_id, &heal_opts).await {
|
||||
Ok((result, error)) => {
|
||||
if let Some(e) = error {
|
||||
error!("EC decode heal failed: {}/{} - {}", bucket, object, e);
|
||||
{
|
||||
let mut progress = self.progress.write().await;
|
||||
progress.update_progress(3, 3, 0, 0);
|
||||
}
|
||||
return Err(Error::TaskExecutionFailed {
|
||||
message: format!("Failed to heal EC decode {}/{}: {}", bucket, object, e),
|
||||
});
|
||||
}
|
||||
|
||||
let object_size = result.object_size as u64;
|
||||
info!("EC decode heal completed successfully: {}/{} ({} bytes, {} drives)",
|
||||
bucket, object, object_size, result.after.drives.len());
|
||||
|
||||
{
|
||||
let mut progress = self.progress.write().await;
|
||||
progress.update_progress(3, 3, object_size, object_size);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => {
|
||||
error!("EC decode heal failed: {}/{} - {}", bucket, object, e);
|
||||
{
|
||||
let mut progress = self.progress.write().await;
|
||||
progress.update_progress(3, 3, 0, 0);
|
||||
}
|
||||
Err(Error::TaskExecutionFailed {
|
||||
message: format!("Failed to heal EC decode {}/{}: {}", bucket, object, e),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -510,10 +510,10 @@ impl Scanner {
|
||||
let req = HealRequest::disk(disk.endpoint().clone());
|
||||
match heal_manager.submit_heal_request(req).await {
|
||||
Ok(task_id) => {
|
||||
warn!("磁盘离线,已自动提交heal任务: {} 磁盘: {}", task_id, disk_path);
|
||||
warn!("disk offline, submit heal task: {} {}", task_id, disk_path);
|
||||
}
|
||||
Err(e) => {
|
||||
error!("磁盘离线,heal任务提交失败: {},错误: {}", disk_path, e);
|
||||
error!("disk offline, submit heal task failed: {} {}", disk_path, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -554,10 +554,10 @@ impl Scanner {
|
||||
);
|
||||
match heal_manager.submit_heal_request(req).await {
|
||||
Ok(task_id) => {
|
||||
warn!("磁盘访问失败,已自动提交heal任务: {} 磁盘: {} 错误: {}", task_id, disk_path, e);
|
||||
warn!("disk access failed, submit heal task: {} {}", task_id, disk_path);
|
||||
}
|
||||
Err(heal_err) => {
|
||||
error!("磁盘访问失败,heal任务提交失败: {},错误: {}", disk_path, heal_err);
|
||||
error!("disk access failed, submit heal task failed: {} {}", disk_path, heal_err);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -682,10 +682,10 @@ impl Scanner {
|
||||
let req = HealRequest::metadata(bucket.to_string(), entry.name.clone());
|
||||
match heal_manager.submit_heal_request(req).await {
|
||||
Ok(task_id) => {
|
||||
warn!("对象元数据损坏,已自动提交heal任务: {} {} / {}", task_id, bucket, entry.name);
|
||||
warn!("object metadata damaged, submit heal task: {} {} / {}", task_id, bucket, entry.name);
|
||||
}
|
||||
Err(e) => {
|
||||
error!("对象元数据损坏,heal任务提交失败: {} / {},错误: {}", bucket, entry.name, e);
|
||||
error!("object metadata damaged, submit heal task failed: {} / {} {}", bucket, entry.name, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -705,10 +705,10 @@ impl Scanner {
|
||||
let req = HealRequest::metadata(bucket.to_string(), entry.name.clone());
|
||||
match heal_manager.submit_heal_request(req).await {
|
||||
Ok(task_id) => {
|
||||
warn!("对象元数据解析失败,已自动提交heal任务: {} {} / {}", task_id, bucket, entry.name);
|
||||
warn!("object metadata parse failed, submit heal task: {} {} / {}", task_id, bucket, entry.name);
|
||||
}
|
||||
Err(e) => {
|
||||
error!("对象元数据解析失败,heal任务提交失败: {} / {},错误: {}", bucket, entry.name, e);
|
||||
error!("object metadata parse failed, submit heal task failed: {} / {} {}", bucket, entry.name, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -817,7 +817,7 @@ impl Scanner {
|
||||
warn!("Object {}/{} missing from disks: {:?}", bucket, object_name, missing_disks);
|
||||
println!("Object {bucket}/{object_name} missing from disks: {missing_disks:?}");
|
||||
|
||||
// 自动提交heal任务
|
||||
// submit heal task
|
||||
let enable_healing = self.config.read().await.enable_healing;
|
||||
if enable_healing {
|
||||
if let Some(heal_manager) = &self.heal_manager {
|
||||
@@ -833,11 +833,11 @@ impl Scanner {
|
||||
);
|
||||
match heal_manager.submit_heal_request(req).await {
|
||||
Ok(task_id) => {
|
||||
warn!("对象缺失,已自动提交heal任务: {} {} / {} (缺失磁盘: {:?})",
|
||||
warn!("object missing, submit heal task: {} {} / {} (missing disks: {:?})",
|
||||
task_id, bucket, object_name, missing_disks);
|
||||
}
|
||||
Err(e) => {
|
||||
error!("对象缺失,heal任务提交失败: {} / {},错误: {}", bucket, object_name, e);
|
||||
error!("object missing, submit heal task failed: {} / {} {}", bucket, object_name, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -969,7 +969,7 @@ impl Scanner {
|
||||
);
|
||||
Ok(())
|
||||
} else {
|
||||
// 自动提交heal任务
|
||||
// submit heal task
|
||||
let enable_healing = self.config.read().await.enable_healing;
|
||||
if enable_healing {
|
||||
if let Some(heal_manager) = &self.heal_manager {
|
||||
@@ -977,10 +977,10 @@ impl Scanner {
|
||||
let req = HealRequest::ec_decode(bucket.to_string(), object.to_string(), None);
|
||||
match heal_manager.submit_heal_request(req).await {
|
||||
Ok(task_id) => {
|
||||
warn!("EC decode失败,已自动提交heal任务: {} {} / {}", task_id, bucket, object);
|
||||
warn!("EC decode failed, submit heal task: {} {} / {}", task_id, bucket, object);
|
||||
}
|
||||
Err(e) => {
|
||||
error!("EC decode失败,heal任务提交失败: {} / {},错误: {}", bucket, object, e);
|
||||
error!("EC decode failed, submit heal task failed: {} / {} {}", bucket, object, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,353 @@
|
||||
use rustfs_ahm::heal::{
|
||||
manager::HealManager,
|
||||
storage::{ECStoreHealStorage, HealStorageAPI},
|
||||
task::{HealOptions, HealPriority, HealRequest, HealType, HEAL_NORMAL_SCAN},
|
||||
};
|
||||
use rustfs_ecstore::{
|
||||
disk::endpoint::Endpoint,
|
||||
endpoints::{EndpointServerPools, Endpoints, PoolEndpoints},
|
||||
store::ECStore,
|
||||
store_api::{PutObjReader, ObjectOptions, StorageAPI, ObjectIO},
|
||||
};
|
||||
use std::{path::PathBuf, sync::Arc, time::Duration};
|
||||
use tokio::fs;
|
||||
use tracing::info;
|
||||
|
||||
/// Test helper: Create test environment with ECStore
|
||||
async fn setup_test_env() -> (Vec<PathBuf>, Arc<ECStore>, Arc<ECStoreHealStorage>) {
|
||||
// create temp dir as 4 disks
|
||||
let test_base_dir = "/tmp/rustfs_ahm_heal_test";
|
||||
let temp_dir = std::path::PathBuf::from(test_base_dir);
|
||||
if temp_dir.exists() {
|
||||
fs::remove_dir_all(&temp_dir).await.unwrap();
|
||||
}
|
||||
fs::create_dir_all(&temp_dir).await.unwrap();
|
||||
|
||||
// create 4 disk dirs
|
||||
let disk_paths = vec![
|
||||
temp_dir.join("disk1"),
|
||||
temp_dir.join("disk2"),
|
||||
temp_dir.join("disk3"),
|
||||
temp_dir.join("disk4"),
|
||||
];
|
||||
|
||||
for disk_path in &disk_paths {
|
||||
fs::create_dir_all(disk_path).await.unwrap();
|
||||
}
|
||||
|
||||
// create EndpointServerPools
|
||||
let mut endpoints = Vec::new();
|
||||
for (i, disk_path) in disk_paths.iter().enumerate() {
|
||||
let mut endpoint = Endpoint::try_from(disk_path.to_str().unwrap()).unwrap();
|
||||
// set correct index
|
||||
endpoint.set_pool_index(0);
|
||||
endpoint.set_set_index(0);
|
||||
endpoint.set_disk_index(i);
|
||||
endpoints.push(endpoint);
|
||||
}
|
||||
|
||||
let pool_endpoints = PoolEndpoints {
|
||||
legacy: false,
|
||||
set_count: 1,
|
||||
drives_per_set: 4,
|
||||
endpoints: Endpoints::from(endpoints),
|
||||
cmd_line: "test".to_string(),
|
||||
platform: format!("OS: {} | Arch: {}", std::env::consts::OS, std::env::consts::ARCH),
|
||||
};
|
||||
|
||||
let endpoint_pools = EndpointServerPools(vec![pool_endpoints]);
|
||||
|
||||
// format disks
|
||||
rustfs_ecstore::store::init_local_disks(endpoint_pools.clone()).await.unwrap();
|
||||
|
||||
// create ECStore with dynamic port
|
||||
let port = 9001;
|
||||
let server_addr: std::net::SocketAddr = format!("127.0.0.1:{port}").parse().unwrap();
|
||||
let ecstore = ECStore::new(server_addr, endpoint_pools).await.unwrap();
|
||||
|
||||
// init bucket metadata system
|
||||
let buckets_list = ecstore
|
||||
.list_bucket(&rustfs_ecstore::store_api::BucketOptions {
|
||||
no_metadata: true,
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
let buckets = buckets_list.into_iter().map(|v| v.name).collect();
|
||||
rustfs_ecstore::bucket::metadata_sys::init_bucket_metadata_sys(ecstore.clone(), buckets).await;
|
||||
|
||||
// Create heal storage layer
|
||||
let heal_storage = Arc::new(ECStoreHealStorage::new(ecstore.clone()));
|
||||
|
||||
(disk_paths, ecstore, heal_storage)
|
||||
}
|
||||
|
||||
/// Test helper: Create a test bucket
|
||||
async fn create_test_bucket(ecstore: &Arc<ECStore>, bucket_name: &str) {
|
||||
(&**ecstore)
|
||||
.make_bucket(bucket_name, &Default::default())
|
||||
.await
|
||||
.expect("Failed to create test bucket");
|
||||
info!("Created test bucket: {}", bucket_name);
|
||||
}
|
||||
|
||||
/// Test helper: Upload test object
|
||||
async fn upload_test_object(
|
||||
ecstore: &Arc<ECStore>,
|
||||
bucket: &str,
|
||||
object: &str,
|
||||
data: &[u8],
|
||||
) {
|
||||
let mut reader = PutObjReader::from_vec(data.to_vec());
|
||||
let object_info = (&**ecstore)
|
||||
.put_object(bucket, object, &mut reader, &ObjectOptions::default())
|
||||
.await
|
||||
.expect("Failed to upload test object");
|
||||
|
||||
info!(
|
||||
"Uploaded test object: {}/{} ({} bytes)",
|
||||
bucket, object, object_info.size
|
||||
);
|
||||
}
|
||||
|
||||
/// Test helper: Cleanup test environment
|
||||
async fn cleanup_test_env(disk_paths: &[PathBuf]) {
|
||||
for disk_path in disk_paths {
|
||||
if disk_path.exists() {
|
||||
fs::remove_dir_all(disk_path).await.expect("Failed to cleanup disk path");
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up test base directory
|
||||
let test_base_dir = PathBuf::from("/tmp/rustfs_ahm_heal_test");
|
||||
if test_base_dir.exists() {
|
||||
fs::remove_dir_all(&test_base_dir).await.expect("Failed to cleanup test base directory");
|
||||
}
|
||||
|
||||
info!("Test environment cleaned up");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_heal_object_basic() {
|
||||
let (disk_paths, ecstore, heal_storage) = setup_test_env().await;
|
||||
|
||||
// Create test bucket and object
|
||||
let bucket_name = "test-bucket";
|
||||
let object_name = "test-object.txt";
|
||||
let test_data = b"Hello, this is test data for healing!";
|
||||
|
||||
create_test_bucket(&ecstore, bucket_name).await;
|
||||
upload_test_object(&ecstore, bucket_name, object_name, test_data).await;
|
||||
|
||||
// Create heal manager
|
||||
let heal_manager = HealManager::new(heal_storage.clone(), Default::default());
|
||||
|
||||
// Submit heal request for the object
|
||||
let heal_request = HealRequest::new(
|
||||
HealType::Object {
|
||||
bucket: bucket_name.to_string(),
|
||||
object: object_name.to_string(),
|
||||
version_id: None,
|
||||
},
|
||||
HealOptions {
|
||||
dry_run: false,
|
||||
recursive: false,
|
||||
remove_corrupted: false,
|
||||
recreate_missing: true,
|
||||
scan_mode: HEAL_NORMAL_SCAN,
|
||||
update_parity: true,
|
||||
timeout: Some(Duration::from_secs(300)),
|
||||
},
|
||||
HealPriority::Normal,
|
||||
);
|
||||
|
||||
let task_id = heal_manager
|
||||
.submit_heal_request(heal_request)
|
||||
.await
|
||||
.expect("Failed to submit heal request");
|
||||
|
||||
info!("Submitted heal request with task ID: {}", task_id);
|
||||
|
||||
// Wait for task completion
|
||||
tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
|
||||
|
||||
// Check task status
|
||||
let task_status = heal_manager.get_task_status(&task_id).await;
|
||||
assert!(task_status.is_ok());
|
||||
|
||||
let status = task_status.unwrap();
|
||||
info!("Task status: {:?}", status);
|
||||
|
||||
// Verify object still exists and is accessible
|
||||
let object_exists = heal_storage.object_exists(bucket_name, object_name).await;
|
||||
assert!(object_exists.is_ok());
|
||||
assert!(object_exists.unwrap());
|
||||
|
||||
// Cleanup
|
||||
cleanup_test_env(&disk_paths).await;
|
||||
|
||||
info!("Heal object basic test passed");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_heal_bucket_basic() {
|
||||
let (disk_paths, ecstore, heal_storage) = setup_test_env().await;
|
||||
|
||||
// Create test bucket
|
||||
let bucket_name = "test-bucket-heal";
|
||||
create_test_bucket(&ecstore, bucket_name).await;
|
||||
|
||||
// Create heal manager
|
||||
let heal_manager = HealManager::new(heal_storage.clone(), Default::default());
|
||||
|
||||
// Submit heal request for the bucket
|
||||
let heal_request = HealRequest::new(
|
||||
HealType::Bucket {
|
||||
bucket: bucket_name.to_string(),
|
||||
},
|
||||
HealOptions {
|
||||
dry_run: false,
|
||||
recursive: true,
|
||||
remove_corrupted: false,
|
||||
recreate_missing: false,
|
||||
scan_mode: HEAL_NORMAL_SCAN,
|
||||
update_parity: false,
|
||||
timeout: Some(Duration::from_secs(300)),
|
||||
},
|
||||
HealPriority::Normal,
|
||||
);
|
||||
|
||||
let task_id = heal_manager
|
||||
.submit_heal_request(heal_request)
|
||||
.await
|
||||
.expect("Failed to submit bucket heal request");
|
||||
|
||||
info!("Submitted bucket heal request with task ID: {}", task_id);
|
||||
|
||||
// Wait for task completion
|
||||
tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
|
||||
|
||||
// Check task status
|
||||
let task_status = heal_manager.get_task_status(&task_id).await;
|
||||
assert!(task_status.is_ok());
|
||||
|
||||
let status = task_status.unwrap();
|
||||
info!("Bucket heal task status: {:?}", status);
|
||||
|
||||
// Verify bucket still exists
|
||||
let bucket_exists = heal_storage.get_bucket_info(bucket_name).await;
|
||||
assert!(bucket_exists.is_ok());
|
||||
assert!(bucket_exists.unwrap().is_some());
|
||||
|
||||
// Cleanup
|
||||
cleanup_test_env(&disk_paths).await;
|
||||
|
||||
info!("Heal bucket basic test passed");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_heal_format_basic() {
|
||||
let (disk_paths, _ecstore, heal_storage) = setup_test_env().await;
|
||||
|
||||
// Create heal manager
|
||||
let heal_manager = HealManager::new(heal_storage.clone(), Default::default());
|
||||
|
||||
// Get disk endpoint for testing
|
||||
let disk_endpoint = Endpoint::try_from(disk_paths[0].to_str().unwrap())
|
||||
.expect("Failed to create disk endpoint");
|
||||
|
||||
// Submit disk heal request (format heal)
|
||||
let heal_request = HealRequest::new(
|
||||
HealType::Disk { endpoint: disk_endpoint },
|
||||
HealOptions {
|
||||
dry_run: true, // Use dry run for format heal test
|
||||
recursive: false,
|
||||
remove_corrupted: false,
|
||||
recreate_missing: false,
|
||||
scan_mode: HEAL_NORMAL_SCAN,
|
||||
update_parity: false,
|
||||
timeout: Some(Duration::from_secs(300)),
|
||||
},
|
||||
HealPriority::Normal,
|
||||
);
|
||||
|
||||
let task_id = heal_manager
|
||||
.submit_heal_request(heal_request)
|
||||
.await
|
||||
.expect("Failed to submit disk heal request");
|
||||
|
||||
info!("Submitted disk heal request with task ID: {}", task_id);
|
||||
|
||||
// Wait for task completion
|
||||
tokio::time::sleep(tokio::time::Duration::from_secs(3)).await;
|
||||
|
||||
// Check task status
|
||||
let task_status = heal_manager.get_task_status(&task_id).await;
|
||||
assert!(task_status.is_ok());
|
||||
|
||||
let status = task_status.unwrap();
|
||||
info!("Disk heal task status: {:?}", status);
|
||||
|
||||
// Cleanup
|
||||
cleanup_test_env(&disk_paths).await;
|
||||
|
||||
info!("Heal format basic test passed");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_heal_storage_api_direct() {
|
||||
let (disk_paths, ecstore, heal_storage) = setup_test_env().await;
|
||||
|
||||
// Test direct heal storage API calls
|
||||
|
||||
// Test heal_format
|
||||
let format_result = heal_storage.heal_format(true).await; // dry run
|
||||
assert!(format_result.is_ok());
|
||||
info!("Direct heal_format test passed");
|
||||
|
||||
// Test heal_bucket
|
||||
let bucket_name = "test-bucket-direct";
|
||||
create_test_bucket(&ecstore, bucket_name).await;
|
||||
|
||||
let heal_opts = rustfs_ecstore::heal::heal_commands::HealOpts {
|
||||
recursive: true,
|
||||
dry_run: true,
|
||||
remove: false,
|
||||
recreate: false,
|
||||
scan_mode: rustfs_ecstore::heal::heal_commands::HEAL_NORMAL_SCAN,
|
||||
update_parity: false,
|
||||
no_lock: false,
|
||||
pool: None,
|
||||
set: None,
|
||||
};
|
||||
|
||||
let bucket_result = heal_storage.heal_bucket(bucket_name, &heal_opts).await;
|
||||
assert!(bucket_result.is_ok());
|
||||
info!("Direct heal_bucket test passed");
|
||||
|
||||
// Test heal_object
|
||||
let object_name = "test-object-direct.txt";
|
||||
let test_data = b"Test data for direct heal API";
|
||||
upload_test_object(&ecstore, bucket_name, object_name, test_data).await;
|
||||
|
||||
let object_heal_opts = rustfs_ecstore::heal::heal_commands::HealOpts {
|
||||
recursive: false,
|
||||
dry_run: true,
|
||||
remove: false,
|
||||
recreate: false,
|
||||
scan_mode: rustfs_ecstore::heal::heal_commands::HEAL_NORMAL_SCAN,
|
||||
update_parity: false,
|
||||
no_lock: false,
|
||||
pool: None,
|
||||
set: None,
|
||||
};
|
||||
|
||||
let object_result = heal_storage.heal_object(bucket_name, object_name, None, &object_heal_opts).await;
|
||||
assert!(object_result.is_ok());
|
||||
info!("Direct heal_object test passed");
|
||||
|
||||
// Cleanup
|
||||
cleanup_test_env(&disk_paths).await;
|
||||
|
||||
info!("Direct heal storage API test passed");
|
||||
}
|
||||
Reference in New Issue
Block a user