test: cover runtime and repair preservation (#3964)

This commit is contained in:
Zhengchao An
2026-06-27 23:34:59 +08:00
committed by GitHub
parent bf03ff2869
commit 82bbef0b60
6 changed files with 270 additions and 47 deletions
+16
View File
@@ -234,4 +234,20 @@ mod tests {
assert!(RuntimeDriveHealthState::Returning.is_snapshot_eligible());
assert!(!RuntimeDriveHealthState::Offline.is_snapshot_eligible());
}
#[test]
fn runtime_drive_health_state_preserves_strict_online_boundary() {
assert!(RuntimeDriveHealthState::Online.is_strictly_online());
assert!(!RuntimeDriveHealthState::Suspect.is_strictly_online());
assert!(!RuntimeDriveHealthState::Returning.is_strictly_online());
assert!(!RuntimeDriveHealthState::Offline.is_strictly_online());
}
#[test]
fn runtime_drive_health_state_preserves_admin_probe_boundary() {
assert!(RuntimeDriveHealthState::Online.should_probe_for_admin());
assert!(RuntimeDriveHealthState::Returning.should_probe_for_admin());
assert!(!RuntimeDriveHealthState::Suspect.should_probe_for_admin());
assert!(!RuntimeDriveHealthState::Offline.should_probe_for_admin());
}
}
+46 -17
View File
@@ -35,6 +35,21 @@ const EVENT_HEAL_CHANNEL_STATE: &str = "heal_channel_state";
const EVENT_HEAL_CHANNEL_REQUEST: &str = "heal_channel_request";
const EVENT_HEAL_CHANNEL_RESPONSE: &str = "heal_channel_response";
fn admission_response(request_id: String, admission: HealAdmissionResult) -> HealChannelResponse {
let (success, error) = match admission {
HealAdmissionResult::Accepted | HealAdmissionResult::Merged => (true, None),
HealAdmissionResult::Full => (false, Some("Heal request queue is full".to_string())),
HealAdmissionResult::Dropped(reason) => (false, Some(format!("Heal request dropped: {}", reason.as_str()))),
};
HealChannelResponse {
request_id,
success,
data: Some(format!("admission={},reason={}", admission.result_label(), admission.reason_label()).into_bytes()),
error,
}
}
/// Heal channel processor
pub struct HealChannelProcessor {
/// Heal manager
@@ -201,22 +216,7 @@ impl HealChannelProcessor {
let _ = response_tx.send(Ok(admission));
let (success, error) = match admission {
HealAdmissionResult::Accepted | HealAdmissionResult::Merged => (true, None),
HealAdmissionResult::Full => (false, Some("Heal request queue is full".to_string())),
HealAdmissionResult::Dropped(reason) => (false, Some(format!("Heal request dropped: {}", reason.as_str()))),
};
let response = HealChannelResponse {
request_id: request.id,
success,
data: Some(
format!("admission={},reason={}", admission.result_label(), admission.reason_label()).into_bytes(),
),
error,
};
self.publish_response(response);
self.publish_response(admission_response(request.id, admission));
}
Err(e) => {
let error_text = e.to_string();
@@ -534,7 +534,7 @@ mod tests {
use crate::heal::manager::HealConfig;
use crate::heal::storage::{HealObjectInfo, HealStorageAPI};
use rustfs_common::heal_channel::{
HealAdmissionResult, HealChannelPriority, HealChannelRequest, HealRequestSource, HealScanMode,
HealAdmissionDropReason, HealAdmissionResult, HealChannelPriority, HealChannelRequest, HealRequestSource, HealScanMode,
};
use std::sync::Arc;
@@ -637,6 +637,35 @@ mod tests {
// If we can get the sender, processor was created correctly
}
#[test]
fn admission_response_preserves_all_admission_outcomes() {
let cases = [
(HealAdmissionResult::Accepted, true, None, "admission=accepted,reason=none"),
(HealAdmissionResult::Merged, true, None, "admission=merged,reason=none"),
(
HealAdmissionResult::Full,
false,
Some("Heal request queue is full"),
"admission=full,reason=none",
),
(
HealAdmissionResult::Dropped(HealAdmissionDropReason::PolicyDropped),
false,
Some("Heal request dropped: policy_dropped"),
"admission=dropped,reason=policy_dropped",
),
];
for (admission, success, error, data) in cases {
let response = admission_response("request-id".to_string(), admission);
assert_eq!(response.request_id, "request-id");
assert_eq!(response.success, success);
assert_eq!(response.error.as_deref(), error);
assert_eq!(response.data.as_deref(), Some(data.as_bytes()));
}
}
#[tokio::test]
async fn test_convert_to_heal_request_bucket() {
let heal_manager = create_test_heal_manager();
+42
View File
@@ -4235,6 +4235,48 @@ mod tests {
);
}
#[tokio::test]
async fn test_submit_heal_request_returns_full_for_normal_priority_when_full() {
let storage: Arc<dyn HealStorageAPI> = Arc::new(MockStorage);
let manager = HealManager::new(
storage,
Some(HealConfig {
queue_size: 1,
..HealConfig::default()
}),
);
let accepted = HealRequest::new(
HealType::Bucket {
bucket: "bucket-a".to_string(),
},
HealOptions::default(),
HealPriority::Normal,
);
let full = HealRequest::new(
HealType::Bucket {
bucket: "bucket-b".to_string(),
},
HealOptions::default(),
HealPriority::Normal,
);
assert_eq!(
manager
.submit_heal_request(accepted)
.await
.expect("first request should be accepted"),
HealAdmissionResult::Accepted
);
assert_eq!(
manager
.submit_heal_request(full)
.await
.expect("normal priority request should surface full admission"),
HealAdmissionResult::Full
);
}
#[tokio::test]
async fn test_high_priority_request_displaces_lower_priority_when_queue_full() {
let storage: Arc<dyn HealStorageAPI> = Arc::new(MockStorage);
+19
View File
@@ -231,4 +231,23 @@ mod tests {
assert_eq!(budget.reason(), Some(ScannerCycleBudgetReason::Directories));
assert!(budget.token().is_cancelled());
}
#[test]
fn first_budget_cancellation_reason_remains_observable() {
let parent = CancellationToken::new();
let budget = ScannerCycleBudget::new(
&parent,
ScannerCycleBudgetConfig {
max_objects: Some(1),
max_directories: Some(0),
..Default::default()
},
);
budget.record_object_scanned();
assert!(!budget.try_start_directory());
assert_eq!(budget.reason(), Some(ScannerCycleBudgetReason::Objects));
assert!(budget.token().is_cancelled());
}
}