mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-28 07:57:01 +00:00
fix(admin): route root heal start through heal_format (#2323)
This commit is contained in:
@@ -24,6 +24,7 @@ use rustfs_common::heal_channel::HealOpts;
|
|||||||
use rustfs_config::MAX_HEAL_REQUEST_SIZE;
|
use rustfs_config::MAX_HEAL_REQUEST_SIZE;
|
||||||
use rustfs_ecstore::bucket::utils::is_valid_object_prefix;
|
use rustfs_ecstore::bucket::utils::is_valid_object_prefix;
|
||||||
use rustfs_ecstore::new_object_layer_fn;
|
use rustfs_ecstore::new_object_layer_fn;
|
||||||
|
use rustfs_ecstore::store_api::HealOperations;
|
||||||
use rustfs_ecstore::store_utils::is_reserved_or_invalid_bucket;
|
use rustfs_ecstore::store_utils::is_reserved_or_invalid_bucket;
|
||||||
use rustfs_policy::policy::action::{Action, AdminAction};
|
use rustfs_policy::policy::action::{Action, AdminAction};
|
||||||
use rustfs_scanner::scanner::{BackgroundHealInfo, read_background_heal_info};
|
use rustfs_scanner::scanner::{BackgroundHealInfo, read_background_heal_info};
|
||||||
@@ -166,6 +167,21 @@ fn validate_heal_request_mode(hip: &HealInitParams) -> S3Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn should_handle_root_heal_directly(hip: &HealInitParams) -> bool {
|
||||||
|
hip.bucket.is_empty() && hip.obj_prefix.is_empty() && hip.client_token.is_empty() && !hip.force_stop
|
||||||
|
}
|
||||||
|
|
||||||
|
fn map_root_heal_status(heal_err: Option<rustfs_ecstore::error::Error>) -> S3Result<()> {
|
||||||
|
match heal_err {
|
||||||
|
None => Ok(()),
|
||||||
|
Some(rustfs_ecstore::error::StorageError::NoHealRequired) => {
|
||||||
|
warn!("root heal completed with non-fatal status: no heal required");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Some(err) => Err(s3_error!(InternalError, "root heal failed: {err}")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn json_response(status: StatusCode, body: Vec<u8>) -> S3Response<(StatusCode, Body)> {
|
fn json_response(status: StatusCode, body: Vec<u8>) -> S3Response<(StatusCode, Body)> {
|
||||||
let mut headers = HeaderMap::new();
|
let mut headers = HeaderMap::new();
|
||||||
headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
|
headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
|
||||||
@@ -207,6 +223,22 @@ impl Operation for HealHandler {
|
|||||||
};
|
};
|
||||||
info!("bytes: {:?}", bytes);
|
info!("bytes: {:?}", bytes);
|
||||||
let hip = extract_heal_init_params(&bytes, &req.uri, params)?;
|
let hip = extract_heal_init_params(&bytes, &req.uri, params)?;
|
||||||
|
// The heal channel currently models bucket/object work. Root heal reuses the
|
||||||
|
// existing format-heal path directly so `/v3/heal/` is accepted intentionally.
|
||||||
|
if should_handle_root_heal_directly(&hip) {
|
||||||
|
let Some(store) = new_object_layer_fn() else {
|
||||||
|
return Err(s3_error!(InternalError, "server not initialized"));
|
||||||
|
};
|
||||||
|
|
||||||
|
let (_, heal_err) = store
|
||||||
|
.heal_format(hip.hs.dry_run)
|
||||||
|
.await
|
||||||
|
.map_err(|e| s3_error!(InternalError, "root heal failed: {e}"))?;
|
||||||
|
|
||||||
|
map_root_heal_status(heal_err)?;
|
||||||
|
|
||||||
|
return Ok(S3Response::new((StatusCode::OK, Body::empty())));
|
||||||
|
}
|
||||||
validate_heal_request_mode(&hip)?;
|
validate_heal_request_mode(&hip)?;
|
||||||
info!("body: {:?}", hip);
|
info!("body: {:?}", hip);
|
||||||
|
|
||||||
@@ -331,14 +363,15 @@ impl Operation for BackgroundHealStatusHandler {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::extract_heal_init_params;
|
use super::extract_heal_init_params;
|
||||||
use super::{
|
use super::{
|
||||||
HealInitParams, HealResp, encode_background_heal_status, json_response, map_heal_response, validate_heal_request_mode,
|
HealInitParams, HealResp, encode_background_heal_status, json_response, map_heal_response, map_root_heal_status,
|
||||||
validate_heal_target,
|
should_handle_root_heal_directly, validate_heal_request_mode, validate_heal_target,
|
||||||
};
|
};
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use http::StatusCode;
|
use http::StatusCode;
|
||||||
use http::Uri;
|
use http::Uri;
|
||||||
use matchit::Router;
|
use matchit::Router;
|
||||||
use rustfs_common::heal_channel::{HealOpts, HealScanMode};
|
use rustfs_common::heal_channel::{HealOpts, HealScanMode};
|
||||||
|
use rustfs_ecstore::error::StorageError;
|
||||||
use rustfs_scanner::scanner::BackgroundHealInfo;
|
use rustfs_scanner::scanner::BackgroundHealInfo;
|
||||||
use s3s::{S3ErrorCode, header::CONTENT_TYPE};
|
use s3s::{S3ErrorCode, header::CONTENT_TYPE};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
@@ -447,6 +480,43 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_should_handle_root_heal_directly_for_root_start_modes() {
|
||||||
|
assert!(should_handle_root_heal_directly(&HealInitParams::default()));
|
||||||
|
assert!(should_handle_root_heal_directly(&HealInitParams {
|
||||||
|
force_start: true,
|
||||||
|
..Default::default()
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_should_handle_root_heal_directly_skips_query_cancel_and_bucket_targets() {
|
||||||
|
assert!(!should_handle_root_heal_directly(&HealInitParams {
|
||||||
|
client_token: "heal-token".to_string(),
|
||||||
|
..Default::default()
|
||||||
|
}));
|
||||||
|
assert!(!should_handle_root_heal_directly(&HealInitParams {
|
||||||
|
force_stop: true,
|
||||||
|
..Default::default()
|
||||||
|
}));
|
||||||
|
assert!(!should_handle_root_heal_directly(&HealInitParams {
|
||||||
|
bucket: "bucket".to_string(),
|
||||||
|
..Default::default()
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_map_root_heal_status_allows_no_heal_required() {
|
||||||
|
map_root_heal_status(Some(StorageError::NoHealRequired)).expect("NoHealRequired should stay non-fatal");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_map_root_heal_status_rejects_fatal_errors() {
|
||||||
|
let err = map_root_heal_status(Some(StorageError::Unexpected)).expect_err("fatal status must fail");
|
||||||
|
assert_eq!(err.code(), &S3ErrorCode::InternalError);
|
||||||
|
assert!(err.to_string().contains("root heal failed: Unexpected error"));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_validate_heal_request_mode_allows_root_query_and_cancel() {
|
fn test_validate_heal_request_mode_allows_root_query_and_cancel() {
|
||||||
validate_heal_request_mode(&HealInitParams {
|
validate_heal_request_mode(&HealInitParams {
|
||||||
|
|||||||
Reference in New Issue
Block a user