mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-24 13:16:28 +00:00
feat: implement heal subsystem for automatic data repair
- Add heal module with core types (HealType, HealRequest, HealTask) - Implement HealManager for task scheduling and execution - Add HealStorageAPI trait and ECStoreHealStorage implementation - Integrate heal capabilities into scanner for automatic repair - Support multiple heal types: object, bucket, disk, metadata, MRF, EC decode - Add progress tracking and event system for heal operations - Merge heal and scanner error types for unified error handling - Include comprehensive logging and metrics for heal operations Signed-off-by: junxiang Mu <1948535941@qq.com>
This commit is contained in:
@@ -0,0 +1,415 @@
|
||||
// Copyright 2024 RustFS Team
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use crate::error::Result;
|
||||
use crate::heal::{progress::HealProgress, storage::HealStorageAPI};
|
||||
use rustfs_ecstore::disk::endpoint::Endpoint;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
sync::Arc,
|
||||
time::{Duration, SystemTime},
|
||||
};
|
||||
use tokio::sync::RwLock;
|
||||
use tracing::{debug, error, info};
|
||||
use uuid::Uuid;
|
||||
|
||||
/// Heal 扫描模式
|
||||
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 类型
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum HealType {
|
||||
/// 对象 heal
|
||||
Object {
|
||||
bucket: String,
|
||||
object: String,
|
||||
version_id: Option<String>,
|
||||
},
|
||||
/// 桶 heal
|
||||
Bucket {
|
||||
bucket: String,
|
||||
},
|
||||
/// 磁盘 heal
|
||||
Disk {
|
||||
endpoint: Endpoint,
|
||||
},
|
||||
/// 元数据 heal
|
||||
Metadata {
|
||||
bucket: String,
|
||||
object: String,
|
||||
},
|
||||
/// MRF heal
|
||||
MRF {
|
||||
meta_path: String,
|
||||
},
|
||||
/// EC 解码 heal
|
||||
ECDecode {
|
||||
bucket: String,
|
||||
object: String,
|
||||
version_id: Option<String>,
|
||||
},
|
||||
}
|
||||
|
||||
/// Heal 优先级
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
pub enum HealPriority {
|
||||
/// 低优先级
|
||||
Low = 0,
|
||||
/// 普通优先级
|
||||
Normal = 1,
|
||||
/// 高优先级
|
||||
High = 2,
|
||||
/// 紧急优先级
|
||||
Urgent = 3,
|
||||
}
|
||||
|
||||
impl Default for HealPriority {
|
||||
fn default() -> Self {
|
||||
Self::Normal
|
||||
}
|
||||
}
|
||||
|
||||
/// Heal 选项
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct HealOptions {
|
||||
/// 扫描模式
|
||||
pub scan_mode: HealScanMode,
|
||||
/// 是否删除损坏数据
|
||||
pub remove_corrupted: bool,
|
||||
/// 是否重新创建
|
||||
pub recreate_missing: bool,
|
||||
/// 是否更新奇偶校验
|
||||
pub update_parity: bool,
|
||||
/// 是否递归处理
|
||||
pub recursive: bool,
|
||||
/// 是否试运行
|
||||
pub dry_run: bool,
|
||||
/// 超时时间
|
||||
pub timeout: Option<Duration>,
|
||||
}
|
||||
|
||||
impl Default for HealOptions {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
scan_mode: HEAL_NORMAL_SCAN,
|
||||
remove_corrupted: false,
|
||||
recreate_missing: true,
|
||||
update_parity: true,
|
||||
recursive: false,
|
||||
dry_run: false,
|
||||
timeout: Some(Duration::from_secs(300)), // 5分钟默认超时
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Heal 任务状态
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum HealTaskStatus {
|
||||
/// 等待中
|
||||
Pending,
|
||||
/// 运行中
|
||||
Running,
|
||||
/// 完成
|
||||
Completed,
|
||||
/// 失败
|
||||
Failed { error: String },
|
||||
/// 取消
|
||||
Cancelled,
|
||||
/// 超时
|
||||
Timeout,
|
||||
}
|
||||
|
||||
/// Heal 请求
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct HealRequest {
|
||||
/// 请求 ID
|
||||
pub id: String,
|
||||
/// Heal 类型
|
||||
pub heal_type: HealType,
|
||||
/// Heal 选项
|
||||
pub options: HealOptions,
|
||||
/// 优先级
|
||||
pub priority: HealPriority,
|
||||
/// 创建时间
|
||||
pub created_at: SystemTime,
|
||||
}
|
||||
|
||||
impl HealRequest {
|
||||
pub fn new(heal_type: HealType, options: HealOptions, priority: HealPriority) -> Self {
|
||||
Self {
|
||||
id: Uuid::new_v4().to_string(),
|
||||
heal_type,
|
||||
options,
|
||||
priority,
|
||||
created_at: SystemTime::now(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn object(bucket: String, object: String, version_id: Option<String>) -> Self {
|
||||
Self::new(
|
||||
HealType::Object {
|
||||
bucket,
|
||||
object,
|
||||
version_id,
|
||||
},
|
||||
HealOptions::default(),
|
||||
HealPriority::Normal,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn bucket(bucket: String) -> Self {
|
||||
Self::new(
|
||||
HealType::Bucket { bucket },
|
||||
HealOptions::default(),
|
||||
HealPriority::Normal,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn disk(endpoint: Endpoint) -> Self {
|
||||
Self::new(
|
||||
HealType::Disk { endpoint },
|
||||
HealOptions::default(),
|
||||
HealPriority::High,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn metadata(bucket: String, object: String) -> Self {
|
||||
Self::new(
|
||||
HealType::Metadata { bucket, object },
|
||||
HealOptions::default(),
|
||||
HealPriority::High,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn ec_decode(bucket: String, object: String, version_id: Option<String>) -> Self {
|
||||
Self::new(
|
||||
HealType::ECDecode {
|
||||
bucket,
|
||||
object,
|
||||
version_id,
|
||||
},
|
||||
HealOptions::default(),
|
||||
HealPriority::Urgent,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Heal 任务
|
||||
pub struct HealTask {
|
||||
/// 任务 ID
|
||||
pub id: String,
|
||||
/// Heal 类型
|
||||
pub heal_type: HealType,
|
||||
/// Heal 选项
|
||||
pub options: HealOptions,
|
||||
/// 任务状态
|
||||
pub status: Arc<RwLock<HealTaskStatus>>,
|
||||
/// 进度跟踪
|
||||
pub progress: Arc<RwLock<HealProgress>>,
|
||||
/// 创建时间
|
||||
pub created_at: SystemTime,
|
||||
/// 开始时间
|
||||
pub started_at: Arc<RwLock<Option<SystemTime>>>,
|
||||
/// 完成时间
|
||||
pub completed_at: Arc<RwLock<Option<SystemTime>>>,
|
||||
/// 取消令牌
|
||||
pub cancel_token: tokio_util::sync::CancellationToken,
|
||||
/// 存储层接口
|
||||
pub storage: Arc<dyn HealStorageAPI>,
|
||||
}
|
||||
|
||||
impl HealTask {
|
||||
pub fn from_request(request: HealRequest, storage: Arc<dyn HealStorageAPI>) -> Self {
|
||||
Self {
|
||||
id: request.id,
|
||||
heal_type: request.heal_type,
|
||||
options: request.options,
|
||||
status: Arc::new(RwLock::new(HealTaskStatus::Pending)),
|
||||
progress: Arc::new(RwLock::new(HealProgress::new())),
|
||||
created_at: request.created_at,
|
||||
started_at: Arc::new(RwLock::new(None)),
|
||||
completed_at: Arc::new(RwLock::new(None)),
|
||||
cancel_token: tokio_util::sync::CancellationToken::new(),
|
||||
storage,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn execute(&self) -> Result<()> {
|
||||
// 更新状态为运行中
|
||||
{
|
||||
let mut status = self.status.write().await;
|
||||
*status = HealTaskStatus::Running;
|
||||
}
|
||||
{
|
||||
let mut started_at = self.started_at.write().await;
|
||||
*started_at = Some(SystemTime::now());
|
||||
}
|
||||
|
||||
info!("Starting heal task: {} with type: {:?}", self.id, self.heal_type);
|
||||
|
||||
let result = match &self.heal_type {
|
||||
HealType::Object { bucket, object, version_id } => {
|
||||
self.heal_object(bucket, object, version_id.as_deref()).await
|
||||
}
|
||||
HealType::Bucket { bucket } => {
|
||||
self.heal_bucket(bucket).await
|
||||
}
|
||||
HealType::Disk { endpoint } => {
|
||||
self.heal_disk(endpoint).await
|
||||
}
|
||||
HealType::Metadata { bucket, object } => {
|
||||
self.heal_metadata(bucket, object).await
|
||||
}
|
||||
HealType::MRF { meta_path } => {
|
||||
self.heal_mrf(meta_path).await
|
||||
}
|
||||
HealType::ECDecode { bucket, object, version_id } => {
|
||||
self.heal_ec_decode(bucket, object, version_id.as_deref()).await
|
||||
}
|
||||
};
|
||||
|
||||
// 更新完成时间和状态
|
||||
{
|
||||
let mut completed_at = self.completed_at.write().await;
|
||||
*completed_at = Some(SystemTime::now());
|
||||
}
|
||||
|
||||
match &result {
|
||||
Ok(_) => {
|
||||
let mut status = self.status.write().await;
|
||||
*status = HealTaskStatus::Completed;
|
||||
info!("Heal task completed successfully: {}", self.id);
|
||||
}
|
||||
Err(e) => {
|
||||
let mut status = self.status.write().await;
|
||||
*status = HealTaskStatus::Failed {
|
||||
error: e.to_string(),
|
||||
};
|
||||
error!("Heal task failed: {} with error: {}", self.id, e);
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
pub async fn cancel(&self) -> Result<()> {
|
||||
self.cancel_token.cancel();
|
||||
let mut status = self.status.write().await;
|
||||
*status = HealTaskStatus::Cancelled;
|
||||
info!("Heal task cancelled: {}", self.id);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_status(&self) -> HealTaskStatus {
|
||||
self.status.read().await.clone()
|
||||
}
|
||||
|
||||
pub async fn get_progress(&self) -> HealProgress {
|
||||
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);
|
||||
|
||||
// 更新进度
|
||||
{
|
||||
let mut progress = self.progress.write().await;
|
||||
progress.set_current_object(Some(format!("{}/{}", bucket, object)));
|
||||
}
|
||||
|
||||
// TODO: 实现具体的对象 heal 逻辑
|
||||
// 1. 检查对象完整性
|
||||
// 2. 如果损坏,尝试 EC 重建
|
||||
// 3. 更新对象数据
|
||||
// 4. 更新进度
|
||||
|
||||
{
|
||||
let mut progress = self.progress.write().await;
|
||||
progress.update_progress(1, 1, 0, 1024); // 示例数据
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn heal_bucket(&self, bucket: &str) -> Result<()> {
|
||||
debug!("Healing bucket: {}", bucket);
|
||||
|
||||
// TODO: 实现桶 heal 逻辑
|
||||
// 1. 检查桶元数据
|
||||
// 2. 修复桶配置
|
||||
// 3. 更新进度
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn heal_disk(&self, endpoint: &Endpoint) -> Result<()> {
|
||||
debug!("Healing disk: {:?}", endpoint);
|
||||
|
||||
// TODO: 实现磁盘 heal 逻辑
|
||||
// 1. 检查磁盘状态
|
||||
// 2. 格式化磁盘(如果需要)
|
||||
// 3. 更新进度
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn heal_metadata(&self, bucket: &str, object: &str) -> Result<()> {
|
||||
debug!("Healing metadata: {}/{}", bucket, object);
|
||||
|
||||
// TODO: 实现元数据 heal 逻辑
|
||||
// 1. 检查元数据完整性
|
||||
// 2. 重建元数据
|
||||
// 3. 更新进度
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn heal_mrf(&self, meta_path: &str) -> Result<()> {
|
||||
debug!("Healing MRF: {}", meta_path);
|
||||
|
||||
// TODO: 实现 MRF heal 逻辑
|
||||
// 1. 检查元数据复制因子
|
||||
// 2. 修复元数据
|
||||
// 3. 更新进度
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn heal_ec_decode(&self, bucket: &str, object: &str, _version_id: Option<&str>) -> Result<()> {
|
||||
debug!("Healing EC decode: {}/{}", bucket, object);
|
||||
|
||||
// TODO: 实现 EC 解码 heal 逻辑
|
||||
// 1. 检查 EC 分片
|
||||
// 2. 使用 EC 算法重建数据
|
||||
// 3. 更新进度
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for HealTask {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("HealTask")
|
||||
.field("id", &self.id)
|
||||
.field("heal_type", &self.heal_type)
|
||||
.field("options", &self.options)
|
||||
.field("created_at", &self.created_at)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user