#[cfg(test)]
mod integration_tests {
type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
use crate::tiering::types::{
AccessPattern, AccessStatistics, IndexMetadata, IndexType, LatencyPercentiles,
PerformanceMetrics,
};
use crate::tiering::{
StorageTier, TierTransitionReason, TieringConfig, TieringManager, TieringPolicy,
};
use std::collections::HashMap;
use std::path::PathBuf;
use std::time::SystemTime;
fn create_test_config() -> (TieringConfig, PathBuf) {
use std::sync::atomic::{AtomicU64, Ordering};
type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
static COUNTER: AtomicU64 = AtomicU64::new(0);
let unique_id = COUNTER.fetch_add(1, Ordering::Relaxed);
let temp_dir = std::env::temp_dir().join(format!(
"oxirs-tiering-test-{}-{}",
std::process::id(),
unique_id
));
let _ = std::fs::remove_dir_all(&temp_dir);
let mut config = TieringConfig::development();
config.storage_base_path = temp_dir.clone();
(config, temp_dir)
}
fn cleanup_test_dir(path: &PathBuf) {
let _ = std::fs::remove_dir_all(path);
}
fn create_test_metadata(id: &str, tier: StorageTier, qps: f64, size_mb: u64) -> IndexMetadata {
IndexMetadata {
index_id: id.to_string(),
current_tier: tier,
size_bytes: size_mb * 1024 * 1024,
compressed_size_bytes: size_mb * 512 * 1024,
vector_count: 100_000,
dimension: 768,
index_type: IndexType::Hnsw,
created_at: SystemTime::now(),
last_accessed: SystemTime::now(),
last_modified: SystemTime::now(),
access_stats: AccessStatistics {
total_queries: (qps * 3600.0) as u64,
queries_last_hour: (qps * 3600.0) as u64,
queries_last_day: (qps * 86400.0) as u64,
queries_last_week: (qps * 604800.0) as u64,
avg_qps: qps,
peak_qps: qps * 2.0,
last_access_time: Some(SystemTime::now()),
access_pattern: if qps > 10.0 {
AccessPattern::Hot
} else if qps > 1.0 {
AccessPattern::Warm
} else {
AccessPattern::Cold
},
query_latencies: LatencyPercentiles::default(),
},
performance_metrics: PerformanceMetrics::default(),
storage_path: None,
custom_metadata: HashMap::new(),
}
}
#[test]
fn test_end_to_end_tiering() -> Result<()> {
let (config, temp_dir) = create_test_config();
let manager = TieringManager::new(config)?;
let hot_index = create_test_metadata("hot_index", StorageTier::Warm, 20.0, 10);
let warm_index = create_test_metadata("warm_index", StorageTier::Cold, 5.0, 50);
let cold_index = create_test_metadata("cold_index", StorageTier::Warm, 0.1, 100);
manager.register_index("hot_index".to_string(), hot_index)?;
manager.register_index("warm_index".to_string(), warm_index)?;
manager.register_index("cold_index".to_string(), cold_index)?;
let data = vec![42u8; 1024];
manager.store_index("hot_index", &data, StorageTier::Warm)?;
manager.store_index("warm_index", &data, StorageTier::Cold)?;
manager.store_index("cold_index", &data, StorageTier::Warm)?;
let recommendations = manager.optimize_tiers()?;
assert!(
!recommendations.is_empty(),
"Should have optimization recommendations"
);
for rec in &recommendations {
assert!(rec.priority >= 0.0, "Priority should be non-negative");
assert!(
matches!(
rec.recommended_tier,
StorageTier::Hot | StorageTier::Warm | StorageTier::Cold
),
"Recommended tier should be valid"
);
}
cleanup_test_dir(&temp_dir);
Ok(())
}
#[test]
fn test_tier_transition_with_different_policies() -> Result<()> {
for policy in &[
TieringPolicy::Lru,
TieringPolicy::Lfu,
TieringPolicy::CostBased,
TieringPolicy::SizeBased,
TieringPolicy::LatencyOptimized,
TieringPolicy::Adaptive,
] {
let (base_config, temp_dir) = create_test_config();
let config = TieringConfig {
policy: *policy,
..base_config
};
let manager = TieringManager::new(config)?;
let metadata = create_test_metadata("test_index", StorageTier::Warm, 15.0, 10);
manager.register_index("test_index".to_string(), metadata)?;
let data = vec![1u8; 1024];
manager.store_index("test_index", &data, StorageTier::Warm)?;
let result = manager.transition_index(
"test_index",
StorageTier::Hot,
TierTransitionReason::HighAccessFrequency,
);
assert!(result.is_ok(), "Policy {:?} failed transition", policy);
let metadata = manager
.get_index_metadata("test_index")
.expect("test_index metadata should exist");
assert_eq!(metadata.current_tier, StorageTier::Hot);
cleanup_test_dir(&temp_dir);
}
Ok(())
}
#[test]
fn test_metrics_collection() -> Result<()> {
let (config, temp_dir) = create_test_config();
let manager = TieringManager::new(config)?;
let metadata = create_test_metadata("test_index", StorageTier::Hot, 10.0, 10);
manager.register_index("test_index".to_string(), metadata)?;
let data = vec![1u8; 1024];
manager.store_index("test_index", &data, StorageTier::Hot)?;
for _ in 0..10 {
manager.load_index("test_index")?;
}
let metrics = manager.get_metrics();
let hot_stats = metrics.get_tier_statistics(StorageTier::Hot);
assert!(hot_stats.total_queries > 0);
assert!(hot_stats.bytes_read > 0);
assert!(hot_stats.bytes_written > 0);
cleanup_test_dir(&temp_dir);
Ok(())
}
#[test]
fn test_capacity_management() -> Result<()> {
let (config, temp_dir) = create_test_config();
let manager = TieringManager::new(config)?;
let stats = manager.get_tier_statistics();
let hot_stats = stats
.get(&StorageTier::Hot)
.expect("Hot tier stats should exist");
assert!(hot_stats.capacity_bytes > 0);
assert_eq!(hot_stats.utilization(), 0.0);
let metadata = create_test_metadata("test_index", StorageTier::Hot, 10.0, 10);
manager.register_index("test_index".to_string(), metadata)?;
let data = vec![1u8; 10 * 1024 * 1024]; manager.store_index("test_index", &data, StorageTier::Hot)?;
manager.apply_optimizations(Some(0))?;
cleanup_test_dir(&temp_dir);
Ok(())
}
#[test]
fn test_auto_optimization() -> Result<()> {
let (base_config, temp_dir) = create_test_config();
let config = TieringConfig {
auto_tier_management: true,
..base_config
};
let manager = TieringManager::new(config)?;
let hot_metadata = create_test_metadata("hot_index", StorageTier::Cold, 100.0, 1);
let cold_metadata = create_test_metadata("cold_index", StorageTier::Hot, 0.01, 100);
manager.register_index("hot_index".to_string(), hot_metadata)?;
manager.register_index("cold_index".to_string(), cold_metadata)?;
let data = vec![1u8; 1024];
manager.store_index("hot_index", &data, StorageTier::Cold)?;
manager.store_index("cold_index", &data, StorageTier::Hot)?;
let applied = manager.apply_optimizations(Some(10))?;
assert!(!applied.is_empty() || applied.is_empty());
cleanup_test_dir(&temp_dir);
Ok(())
}
#[test]
fn test_gradual_transition() -> Result<()> {
let (base_config, temp_dir) = create_test_config();
let config = TieringConfig {
gradual_transition: super::super::types::GradualTransitionConfig {
enabled: true,
stages: 2,
..Default::default()
},
..base_config
};
let manager = TieringManager::new(config)?;
let metadata = create_test_metadata("test_index", StorageTier::Hot, 5.0, 10);
manager.register_index("test_index".to_string(), metadata)?;
let data = vec![1u8; 1024];
manager.store_index("test_index", &data, StorageTier::Hot)?;
let result = manager.transition_index(
"test_index",
StorageTier::Cold,
TierTransitionReason::LowAccessFrequency,
);
assert!(result.is_ok());
cleanup_test_dir(&temp_dir);
Ok(())
}
#[test]
fn test_tier_statistics() -> Result<()> {
let (config, temp_dir) = create_test_config();
let manager = TieringManager::new(config)?;
let stats = manager.get_tier_statistics();
assert_eq!(stats.len(), 3);
for (tier, stat) in stats.iter() {
assert!(stat.capacity_bytes > 0);
assert_eq!(stat.used_bytes, 0); assert_eq!(stat.index_count, 0);
eprintln!("Tier {:?}: capacity={} bytes", tier, stat.capacity_bytes);
}
cleanup_test_dir(&temp_dir);
Ok(())
}
#[test]
fn test_multiple_transitions() -> Result<()> {
let (config, temp_dir) = create_test_config();
let manager = TieringManager::new(config)?;
let metadata = create_test_metadata("test_index", StorageTier::Cold, 10.0, 10);
manager.register_index("test_index".to_string(), metadata)?;
let data = vec![1u8; 1024];
manager.store_index("test_index", &data, StorageTier::Cold)?;
manager.transition_index(
"test_index",
StorageTier::Warm,
TierTransitionReason::CostOptimization,
)?;
let meta = manager
.get_index_metadata("test_index")
.expect("test_index metadata should exist");
assert_eq!(meta.current_tier, StorageTier::Warm);
manager.transition_index(
"test_index",
StorageTier::Hot,
TierTransitionReason::HighAccessFrequency,
)?;
let meta = manager
.get_index_metadata("test_index")
.expect("test_index metadata should exist");
assert_eq!(meta.current_tier, StorageTier::Hot);
manager.transition_index(
"test_index",
StorageTier::Cold,
TierTransitionReason::LowAccessFrequency,
)?;
let meta = manager
.get_index_metadata("test_index")
.expect("test_index metadata should exist");
assert_eq!(meta.current_tier, StorageTier::Cold);
cleanup_test_dir(&temp_dir);
Ok(())
}
}