mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-05 21:07:43 +00:00
a8b7b28fd0
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: loverustfs <hello@rustfs.com> Co-authored-by: houseme <housemecn@gmail.com>
284 lines
10 KiB
Rust
284 lines
10 KiB
Rust
// 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 rustfs_ahm::heal::{
|
|
event::{HealEvent, Severity},
|
|
task::{HealPriority, HealType},
|
|
utils,
|
|
};
|
|
|
|
#[test]
|
|
fn test_heal_event_to_heal_request_no_panic() {
|
|
use rustfs_ecstore::disk::endpoint::Endpoint;
|
|
|
|
// Test that invalid pool/set indices don't cause panic
|
|
// Create endpoint using try_from or similar method
|
|
let endpoint_result = Endpoint::try_from("http://localhost:9000");
|
|
if let Ok(mut endpoint) = endpoint_result {
|
|
endpoint.pool_idx = -1;
|
|
endpoint.set_idx = -1;
|
|
endpoint.disk_idx = 0;
|
|
|
|
let event = HealEvent::DiskStatusChange {
|
|
endpoint,
|
|
old_status: "ok".to_string(),
|
|
new_status: "offline".to_string(),
|
|
};
|
|
|
|
// Should return error instead of panicking
|
|
let result = event.to_heal_request();
|
|
assert!(result.is_err());
|
|
assert!(result.unwrap_err().to_string().contains("Invalid heal type"));
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_heal_event_to_heal_request_valid_indices() {
|
|
use rustfs_ecstore::disk::endpoint::Endpoint;
|
|
|
|
// Test that valid indices work correctly
|
|
let endpoint_result = Endpoint::try_from("http://localhost:9000");
|
|
if let Ok(mut endpoint) = endpoint_result {
|
|
endpoint.pool_idx = 0;
|
|
endpoint.set_idx = 1;
|
|
endpoint.disk_idx = 0;
|
|
|
|
let event = HealEvent::DiskStatusChange {
|
|
endpoint,
|
|
old_status: "ok".to_string(),
|
|
new_status: "offline".to_string(),
|
|
};
|
|
|
|
let result = event.to_heal_request();
|
|
assert!(result.is_ok());
|
|
let request = result.unwrap();
|
|
assert!(matches!(request.heal_type, HealType::ErasureSet { .. }));
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_heal_event_object_corruption() {
|
|
let event = HealEvent::ObjectCorruption {
|
|
bucket: "test-bucket".to_string(),
|
|
object: "test-object".to_string(),
|
|
version_id: None,
|
|
corruption_type: rustfs_ahm::heal::event::CorruptionType::DataCorruption,
|
|
severity: Severity::High,
|
|
};
|
|
|
|
let result = event.to_heal_request();
|
|
assert!(result.is_ok());
|
|
let request = result.unwrap();
|
|
assert!(matches!(request.heal_type, HealType::Object { .. }));
|
|
assert_eq!(request.priority, HealPriority::High);
|
|
}
|
|
|
|
#[test]
|
|
fn test_heal_event_ec_decode_failure() {
|
|
let event = HealEvent::ECDecodeFailure {
|
|
bucket: "test-bucket".to_string(),
|
|
object: "test-object".to_string(),
|
|
version_id: None,
|
|
missing_shards: vec![0, 1],
|
|
available_shards: vec![2, 3],
|
|
};
|
|
|
|
let result = event.to_heal_request();
|
|
assert!(result.is_ok());
|
|
let request = result.unwrap();
|
|
assert!(matches!(request.heal_type, HealType::ECDecode { .. }));
|
|
assert_eq!(request.priority, HealPriority::Urgent);
|
|
}
|
|
|
|
#[test]
|
|
fn test_format_set_disk_id_from_i32_negative() {
|
|
// Test that negative indices return None
|
|
assert!(utils::format_set_disk_id_from_i32(-1, 0).is_none());
|
|
assert!(utils::format_set_disk_id_from_i32(0, -1).is_none());
|
|
assert!(utils::format_set_disk_id_from_i32(-1, -1).is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn test_format_set_disk_id_from_i32_valid() {
|
|
// Test that valid indices return Some
|
|
let result = utils::format_set_disk_id_from_i32(0, 1);
|
|
assert!(result.is_some());
|
|
assert_eq!(result.unwrap(), "pool_0_set_1");
|
|
}
|
|
|
|
#[test]
|
|
fn test_resume_state_timestamp_handling() {
|
|
use rustfs_ahm::heal::resume::ResumeState;
|
|
|
|
// Test that ResumeState creation doesn't panic even if system time is before epoch
|
|
// This is a theoretical test - in practice, system time should never be before epoch
|
|
// But we want to ensure unwrap_or_default handles edge cases
|
|
let state = ResumeState::new(
|
|
"test-task".to_string(),
|
|
"test-type".to_string(),
|
|
"pool_0_set_1".to_string(),
|
|
vec!["bucket1".to_string()],
|
|
);
|
|
|
|
// Verify fields are initialized (u64 is always >= 0)
|
|
// The important thing is that unwrap_or_default prevents panic
|
|
let _ = state.start_time;
|
|
let _ = state.last_update;
|
|
}
|
|
|
|
#[test]
|
|
fn test_resume_checkpoint_timestamp_handling() {
|
|
use rustfs_ahm::heal::resume::ResumeCheckpoint;
|
|
|
|
// Test that ResumeCheckpoint creation doesn't panic
|
|
let checkpoint = ResumeCheckpoint::new("test-task".to_string());
|
|
|
|
// Verify field is initialized (u64 is always >= 0)
|
|
// The important thing is that unwrap_or_default prevents panic
|
|
let _ = checkpoint.checkpoint_time;
|
|
}
|
|
|
|
#[test]
|
|
fn test_path_to_str_helper() {
|
|
use std::path::Path;
|
|
|
|
// Test that path conversion handles non-UTF-8 paths gracefully
|
|
// Note: This is a compile-time test - actual non-UTF-8 paths are hard to construct in Rust
|
|
// The helper function should properly handle the conversion
|
|
let valid_path = Path::new("test/path");
|
|
assert!(valid_path.to_str().is_some());
|
|
}
|
|
|
|
#[test]
|
|
fn test_heal_task_status_atomic_update() {
|
|
use rustfs_ahm::heal::storage::HealStorageAPI;
|
|
use rustfs_ahm::heal::task::{HealOptions, HealRequest, HealTask, HealTaskStatus};
|
|
use std::sync::Arc;
|
|
|
|
// Mock storage for testing
|
|
struct MockStorage;
|
|
#[async_trait::async_trait]
|
|
impl HealStorageAPI for MockStorage {
|
|
async fn get_object_meta(
|
|
&self,
|
|
_bucket: &str,
|
|
_object: &str,
|
|
) -> rustfs_ahm::Result<Option<rustfs_ecstore::store_api::ObjectInfo>> {
|
|
Ok(None)
|
|
}
|
|
async fn get_object_data(&self, _bucket: &str, _object: &str) -> rustfs_ahm::Result<Option<Vec<u8>>> {
|
|
Ok(None)
|
|
}
|
|
async fn put_object_data(&self, _bucket: &str, _object: &str, _data: &[u8]) -> rustfs_ahm::Result<()> {
|
|
Ok(())
|
|
}
|
|
async fn delete_object(&self, _bucket: &str, _object: &str) -> rustfs_ahm::Result<()> {
|
|
Ok(())
|
|
}
|
|
async fn verify_object_integrity(&self, _bucket: &str, _object: &str) -> rustfs_ahm::Result<bool> {
|
|
Ok(true)
|
|
}
|
|
async fn ec_decode_rebuild(&self, _bucket: &str, _object: &str) -> rustfs_ahm::Result<Vec<u8>> {
|
|
Ok(vec![])
|
|
}
|
|
async fn get_disk_status(
|
|
&self,
|
|
_endpoint: &rustfs_ecstore::disk::endpoint::Endpoint,
|
|
) -> rustfs_ahm::Result<rustfs_ahm::heal::storage::DiskStatus> {
|
|
Ok(rustfs_ahm::heal::storage::DiskStatus::Ok)
|
|
}
|
|
async fn format_disk(&self, _endpoint: &rustfs_ecstore::disk::endpoint::Endpoint) -> rustfs_ahm::Result<()> {
|
|
Ok(())
|
|
}
|
|
async fn get_bucket_info(&self, _bucket: &str) -> rustfs_ahm::Result<Option<rustfs_ecstore::store_api::BucketInfo>> {
|
|
Ok(None)
|
|
}
|
|
async fn heal_bucket_metadata(&self, _bucket: &str) -> rustfs_ahm::Result<()> {
|
|
Ok(())
|
|
}
|
|
async fn list_buckets(&self) -> rustfs_ahm::Result<Vec<rustfs_ecstore::store_api::BucketInfo>> {
|
|
Ok(vec![])
|
|
}
|
|
async fn object_exists(&self, _bucket: &str, _object: &str) -> rustfs_ahm::Result<bool> {
|
|
Ok(false)
|
|
}
|
|
async fn get_object_size(&self, _bucket: &str, _object: &str) -> rustfs_ahm::Result<Option<u64>> {
|
|
Ok(None)
|
|
}
|
|
async fn get_object_checksum(&self, _bucket: &str, _object: &str) -> rustfs_ahm::Result<Option<String>> {
|
|
Ok(None)
|
|
}
|
|
async fn heal_object(
|
|
&self,
|
|
_bucket: &str,
|
|
_object: &str,
|
|
_version_id: Option<&str>,
|
|
_opts: &rustfs_common::heal_channel::HealOpts,
|
|
) -> rustfs_ahm::Result<(rustfs_madmin::heal_commands::HealResultItem, Option<rustfs_ahm::Error>)> {
|
|
Ok((rustfs_madmin::heal_commands::HealResultItem::default(), None))
|
|
}
|
|
async fn heal_bucket(
|
|
&self,
|
|
_bucket: &str,
|
|
_opts: &rustfs_common::heal_channel::HealOpts,
|
|
) -> rustfs_ahm::Result<rustfs_madmin::heal_commands::HealResultItem> {
|
|
Ok(rustfs_madmin::heal_commands::HealResultItem::default())
|
|
}
|
|
async fn heal_format(
|
|
&self,
|
|
_dry_run: bool,
|
|
) -> rustfs_ahm::Result<(rustfs_madmin::heal_commands::HealResultItem, Option<rustfs_ahm::Error>)> {
|
|
Ok((rustfs_madmin::heal_commands::HealResultItem::default(), None))
|
|
}
|
|
async fn list_objects_for_heal(&self, _bucket: &str, _prefix: &str) -> rustfs_ahm::Result<Vec<String>> {
|
|
Ok(vec![])
|
|
}
|
|
async fn list_objects_for_heal_page(
|
|
&self,
|
|
_bucket: &str,
|
|
_prefix: &str,
|
|
_continuation_token: Option<&str>,
|
|
) -> rustfs_ahm::Result<(Vec<String>, Option<String>, bool)> {
|
|
Ok((vec![], None, false))
|
|
}
|
|
async fn get_disk_for_resume(&self, _set_disk_id: &str) -> rustfs_ahm::Result<rustfs_ecstore::disk::DiskStore> {
|
|
Err(rustfs_ahm::Error::other("Not implemented in mock"))
|
|
}
|
|
}
|
|
|
|
// Create a heal request and task
|
|
let request = HealRequest::new(
|
|
HealType::Object {
|
|
bucket: "test-bucket".to_string(),
|
|
object: "test-object".to_string(),
|
|
version_id: None,
|
|
},
|
|
HealOptions::default(),
|
|
HealPriority::Normal,
|
|
);
|
|
|
|
let storage: Arc<dyn HealStorageAPI> = Arc::new(MockStorage);
|
|
let task = HealTask::from_request(request, storage);
|
|
|
|
// Verify initial status
|
|
let status = tokio::runtime::Runtime::new().unwrap().block_on(task.get_status());
|
|
assert_eq!(status, HealTaskStatus::Pending);
|
|
|
|
// The task should have task_start_instant field initialized
|
|
// This is an internal detail, but we can verify it doesn't cause issues
|
|
// by checking that the task can be created successfully
|
|
// Note: We can't directly access private fields, but creation without panic
|
|
// confirms the fix works
|
|
}
|