// 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 std::{collections::HashMap, time::SystemTime}; use serde::{Deserialize, Serialize}; use time::OffsetDateTime; use crate::metrics::TimedAction; #[derive(Debug, PartialEq, Clone, Copy)] pub enum ItemState { Offline, Initializing, Online, } impl ItemState { pub fn to_string(&self) -> &str { match self { ItemState::Offline => "offline", ItemState::Initializing => "initializing", ItemState::Online => "online", } } pub fn from_string(s: &str) -> Option { match s { "offline" => Some(ItemState::Offline), "initializing" => Some(ItemState::Initializing), "online" => Some(ItemState::Online), _ => None, } } } #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)] pub struct DiskMetrics { pub last_minute: HashMap, pub api_calls: HashMap, pub total_waiting: u32, pub total_errors_availability: u64, pub total_errors_timeout: u64, pub total_writes: u64, pub total_deletes: u64, } #[derive(Serialize, Deserialize, Debug, Default, Clone)] pub struct Disk { pub endpoint: String, #[serde(rename = "rootDisk")] pub root_disk: bool, #[serde(rename = "path")] pub drive_path: String, pub healing: bool, pub scanning: bool, pub state: String, pub uuid: String, pub major: u32, pub minor: u32, pub model: Option, #[serde(rename = "totalspace")] pub total_space: u64, #[serde(rename = "usedspace")] pub used_space: u64, #[serde(rename = "availspace")] pub available_space: u64, #[serde(rename = "readthroughput")] pub read_throughput: f64, #[serde(rename = "writethroughput")] pub write_throughput: f64, #[serde(rename = "readlatency")] pub read_latency: f64, #[serde(rename = "writelatency")] pub write_latency: f64, pub utilization: f64, pub metrics: Option, pub heal_info: Option, pub used_inodes: u64, pub free_inodes: u64, pub local: bool, pub pool_index: i32, pub set_index: i32, pub disk_index: i32, } #[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct HealingDisk { pub id: String, pub heal_id: String, pub pool_index: Option, pub set_index: Option, pub disk_index: Option, pub endpoint: String, pub path: String, pub started: Option, pub last_update: Option, pub retry_attempts: u64, pub objects_total_count: u64, pub objects_total_size: u64, pub items_healed: u64, pub items_failed: u64, pub item_skipped: u64, pub bytes_done: u64, pub bytes_failed: u64, pub bytes_skipped: u64, pub objects_healed: u64, pub objects_failed: u64, pub bucket: String, pub object: String, pub queue_buckets: Vec, pub healed_buckets: Vec, pub finished: bool, } #[derive(Debug, Default, Serialize, Deserialize)] pub enum BackendByte { #[default] Unknown, FS, Erasure, } #[derive(Debug, Default, Serialize, Deserialize)] pub struct StorageInfo { pub disks: Vec, pub backend: BackendInfo, } #[derive(Debug, Default, Serialize, Deserialize)] pub struct BackendDisks(pub HashMap); impl BackendDisks { pub fn new() -> Self { Self(HashMap::new()) } pub fn sum(&self) -> usize { self.0.values().sum() } } #[derive(Debug, Default, Serialize, Deserialize)] #[serde(rename_all = "PascalCase", default)] pub struct BackendInfo { pub backend_type: BackendByte, pub online_disks: BackendDisks, pub offline_disks: BackendDisks, #[serde(rename = "StandardSCData")] pub standard_sc_data: Vec, #[serde(rename = "StandardSCParities")] pub standard_sc_parities: Vec, #[serde(rename = "StandardSCParity")] pub standard_sc_parity: Option, #[serde(rename = "RRSCData")] pub rr_sc_data: Vec, #[serde(rename = "RRSCParities")] pub rr_sc_parities: Vec, #[serde(rename = "RRSCParity")] pub rr_sc_parity: Option, pub total_sets: Vec, pub drives_per_set: Vec, } pub const ITEM_OFFLINE: &str = "offline"; pub const ITEM_INITIALIZING: &str = "initializing"; pub const ITEM_ONLINE: &str = "online"; #[derive(Debug, Default, Serialize, Deserialize)] pub struct MemStats { pub alloc: u64, pub total_alloc: u64, pub mallocs: u64, pub frees: u64, pub heap_alloc: u64, } #[derive(Debug, Default, Serialize, Deserialize)] pub struct ServerProperties { pub state: String, pub endpoint: String, pub scheme: String, pub uptime: u64, pub version: String, #[serde(rename = "commitID")] pub commit_id: String, pub network: HashMap, #[serde(rename = "drives")] pub disks: Vec, #[serde(rename = "poolNumber")] pub pool_number: i32, #[serde(rename = "poolNumbers")] pub pool_numbers: Vec, pub mem_stats: MemStats, pub max_procs: u64, pub num_cpu: u64, pub runtime_version: String, pub rustfs_env_vars: HashMap, } #[derive(Serialize, Deserialize, Debug, Default)] pub struct Kms { pub status: Option, pub encrypt: Option, pub decrypt: Option, pub endpoint: Option, pub version: Option, } #[derive(Serialize, Deserialize, Debug, Default)] pub struct Ldap { pub status: Option, } #[derive(Serialize, Deserialize, Debug, Default)] pub struct Status { pub status: Option, } pub type Audit = HashMap; pub type Logger = HashMap; pub type TargetIDStatus = HashMap; #[derive(Serialize, Deserialize, Default, Debug)] pub struct Services { pub kms: Option, // deprecated july 2023 #[serde(rename = "kmsStatus")] pub kms_status: Option>, pub ldap: Option, pub logger: Option>, pub audit: Option>, pub notifications: Option>>>, } #[derive(Serialize, Deserialize, Debug, Default)] pub struct Buckets { pub count: u64, pub error: Option, } #[derive(Serialize, Deserialize, Debug, Default)] pub struct Objects { pub count: u64, pub error: Option, } #[derive(Serialize, Deserialize, Debug, Default)] pub struct Versions { pub count: u64, pub error: Option, } #[derive(Serialize, Deserialize, Debug, Default)] pub struct DeleteMarkers { pub count: u64, pub error: Option, } #[derive(Serialize, Deserialize, Debug, Default)] pub struct Usage { pub size: u64, pub error: Option, } #[derive(Serialize, Deserialize, Debug, Default)] pub struct ErasureSetInfo { pub id: i32, #[serde(rename = "rawUsage")] pub raw_usage: u64, #[serde(rename = "rawCapacity")] pub raw_capacity: u64, pub usage: u64, #[serde(rename = "objectsCount")] pub objects_count: u64, #[serde(rename = "versionsCount")] pub versions_count: u64, #[serde(rename = "deleteMarkersCount")] pub delete_markers_count: u64, #[serde(rename = "healDisks")] pub heal_disks: i32, } #[derive(Serialize, Deserialize, Debug, Default)] pub enum BackendType { #[default] #[serde(rename = "FS")] FsType, #[serde(rename = "Erasure")] ErasureType, } #[derive(Serialize, Deserialize)] pub struct FSBackend { #[serde(rename = "backendType")] pub backend_type: BackendType, } #[derive(Serialize, Deserialize, Debug, Default)] pub struct ErasureBackend { #[serde(rename = "backendType")] pub backend_type: BackendType, #[serde(rename = "onlineDisks")] pub online_disks: usize, #[serde(rename = "offlineDisks")] pub offline_disks: usize, #[serde(rename = "standardSCParity")] pub standard_sc_parity: Option, #[serde(rename = "rrSCParity")] pub rr_sc_parity: Option, #[serde(rename = "totalSets")] pub total_sets: Vec, #[serde(rename = "totalDrivesPerSet")] pub drives_per_set: Vec, } #[derive(Serialize, Deserialize)] pub struct InfoMessage { pub mode: Option, pub domain: Option>, pub region: Option, #[serde(rename = "sqsARN")] pub sqs_arn: Option>, #[serde(rename = "deploymentID")] pub deployment_id: Option, pub buckets: Option, pub objects: Option, pub versions: Option, #[serde(rename = "deletemarkers")] pub delete_markers: Option, pub usage: Option, pub services: Option, pub backend: Option, pub servers: Option>, pub pools: Option>>, } #[cfg(test)] mod tests { use super::*; use serde_json; use std::collections::HashMap; use time::OffsetDateTime; #[test] fn test_item_state_to_string() { assert_eq!(ItemState::Offline.to_string(), ITEM_OFFLINE); assert_eq!(ItemState::Initializing.to_string(), ITEM_INITIALIZING); assert_eq!(ItemState::Online.to_string(), ITEM_ONLINE); } #[test] fn test_item_state_from_string_valid() { assert_eq!(ItemState::from_string(ITEM_OFFLINE), Some(ItemState::Offline)); assert_eq!(ItemState::from_string(ITEM_INITIALIZING), Some(ItemState::Initializing)); assert_eq!(ItemState::from_string(ITEM_ONLINE), Some(ItemState::Online)); } #[test] fn test_item_state_from_string_invalid() { assert_eq!(ItemState::from_string("invalid"), None); assert_eq!(ItemState::from_string(""), None); assert_eq!(ItemState::from_string("OFFLINE"), None); // Case sensitive } #[test] fn test_disk_metrics_default() { let metrics = DiskMetrics::default(); assert!(metrics.last_minute.is_empty()); assert!(metrics.api_calls.is_empty()); assert_eq!(metrics.total_waiting, 0); assert_eq!(metrics.total_errors_availability, 0); assert_eq!(metrics.total_errors_timeout, 0); assert_eq!(metrics.total_writes, 0); assert_eq!(metrics.total_deletes, 0); } #[test] fn test_disk_metrics_with_values() { let mut last_minute = HashMap::new(); last_minute.insert("read".to_string(), TimedAction::default()); let mut api_calls = HashMap::new(); api_calls.insert("GET".to_string(), 100); api_calls.insert("PUT".to_string(), 50); let metrics = DiskMetrics { last_minute, api_calls, total_waiting: 5, total_errors_availability: 2, total_errors_timeout: 1, total_writes: 1000, total_deletes: 50, }; assert_eq!(metrics.last_minute.len(), 1); assert_eq!(metrics.api_calls.len(), 2); assert_eq!(metrics.total_waiting, 5); assert_eq!(metrics.total_writes, 1000); assert_eq!(metrics.total_deletes, 50); } #[test] fn test_disk_default() { let disk = Disk::default(); assert!(disk.endpoint.is_empty()); assert!(!disk.root_disk); assert!(disk.drive_path.is_empty()); assert!(!disk.healing); assert!(!disk.scanning); assert!(disk.state.is_empty()); assert!(disk.uuid.is_empty()); assert_eq!(disk.major, 0); assert_eq!(disk.minor, 0); assert!(disk.model.is_none()); assert_eq!(disk.total_space, 0); assert_eq!(disk.used_space, 0); assert_eq!(disk.available_space, 0); assert_eq!(disk.read_throughput, 0.0); assert_eq!(disk.write_throughput, 0.0); assert_eq!(disk.read_latency, 0.0); assert_eq!(disk.write_latency, 0.0); assert_eq!(disk.utilization, 0.0); assert!(disk.metrics.is_none()); assert!(disk.heal_info.is_none()); assert_eq!(disk.used_inodes, 0); assert_eq!(disk.free_inodes, 0); assert!(!disk.local); assert_eq!(disk.pool_index, 0); assert_eq!(disk.set_index, 0); assert_eq!(disk.disk_index, 0); } #[test] fn test_disk_with_values() { let disk = Disk { endpoint: "http://localhost:9000".to_string(), root_disk: true, drive_path: "/data/disk1".to_string(), healing: false, scanning: true, state: "online".to_string(), uuid: "12345678-1234-1234-1234-123456789abc".to_string(), major: 8, minor: 1, model: Some("Samsung SSD 980".to_string()), total_space: 1000000000000, used_space: 500000000000, available_space: 500000000000, read_throughput: 100.5, write_throughput: 80.3, read_latency: 5.2, write_latency: 7.8, utilization: 50.0, metrics: Some(DiskMetrics::default()), heal_info: None, used_inodes: 1000000, free_inodes: 9000000, local: true, pool_index: 0, set_index: 1, disk_index: 2, }; assert_eq!(disk.endpoint, "http://localhost:9000"); assert!(disk.root_disk); assert_eq!(disk.drive_path, "/data/disk1"); assert!(disk.scanning); assert_eq!(disk.state, "online"); assert_eq!(disk.major, 8); assert_eq!(disk.minor, 1); assert_eq!(disk.model.unwrap(), "Samsung SSD 980"); assert_eq!(disk.total_space, 1000000000000); assert_eq!(disk.utilization, 50.0); assert!(disk.metrics.is_some()); assert!(disk.local); } #[test] fn test_healing_disk_default() { let healing_disk = HealingDisk::default(); assert!(healing_disk.id.is_empty()); assert!(healing_disk.heal_id.is_empty()); assert!(healing_disk.pool_index.is_none()); assert!(healing_disk.set_index.is_none()); assert!(healing_disk.disk_index.is_none()); assert!(healing_disk.endpoint.is_empty()); assert!(healing_disk.path.is_empty()); assert!(healing_disk.started.is_none()); assert!(healing_disk.last_update.is_none()); assert_eq!(healing_disk.retry_attempts, 0); assert_eq!(healing_disk.objects_total_count, 0); assert_eq!(healing_disk.objects_total_size, 0); assert_eq!(healing_disk.items_healed, 0); assert_eq!(healing_disk.items_failed, 0); assert_eq!(healing_disk.item_skipped, 0); assert_eq!(healing_disk.bytes_done, 0); assert_eq!(healing_disk.bytes_failed, 0); assert_eq!(healing_disk.bytes_skipped, 0); assert_eq!(healing_disk.objects_healed, 0); assert_eq!(healing_disk.objects_failed, 0); assert!(healing_disk.bucket.is_empty()); assert!(healing_disk.object.is_empty()); assert!(healing_disk.queue_buckets.is_empty()); assert!(healing_disk.healed_buckets.is_empty()); assert!(!healing_disk.finished); } #[test] fn test_healing_disk_with_values() { let now = OffsetDateTime::now_utc(); let system_time = std::time::SystemTime::now(); let healing_disk = HealingDisk { id: "heal-001".to_string(), heal_id: "heal-session-123".to_string(), pool_index: Some(0), set_index: Some(1), disk_index: Some(2), endpoint: "http://node1:9000".to_string(), path: "/data/disk1".to_string(), started: Some(now), last_update: Some(system_time), retry_attempts: 3, objects_total_count: 10000, objects_total_size: 1000000000, items_healed: 8000, items_failed: 100, item_skipped: 50, bytes_done: 800000000, bytes_failed: 10000000, bytes_skipped: 5000000, objects_healed: 7900, objects_failed: 100, bucket: "test-bucket".to_string(), object: "test-object".to_string(), queue_buckets: vec!["bucket1".to_string(), "bucket2".to_string()], healed_buckets: vec!["bucket3".to_string()], finished: false, }; assert_eq!(healing_disk.id, "heal-001"); assert_eq!(healing_disk.heal_id, "heal-session-123"); assert_eq!(healing_disk.pool_index.unwrap(), 0); assert_eq!(healing_disk.set_index.unwrap(), 1); assert_eq!(healing_disk.disk_index.unwrap(), 2); assert_eq!(healing_disk.retry_attempts, 3); assert_eq!(healing_disk.objects_total_count, 10000); assert_eq!(healing_disk.items_healed, 8000); assert_eq!(healing_disk.queue_buckets.len(), 2); assert_eq!(healing_disk.healed_buckets.len(), 1); assert!(!healing_disk.finished); } #[test] fn test_backend_byte_default() { let backend = BackendByte::default(); assert!(matches!(backend, BackendByte::Unknown)); } #[test] fn test_backend_byte_variants() { let unknown = BackendByte::Unknown; let fs = BackendByte::FS; let erasure = BackendByte::Erasure; // Test that all variants can be created assert!(matches!(unknown, BackendByte::Unknown)); assert!(matches!(fs, BackendByte::FS)); assert!(matches!(erasure, BackendByte::Erasure)); } #[test] fn test_storage_info_creation() { let storage_info = StorageInfo { disks: vec![ Disk { endpoint: "node1:9000".to_string(), state: "online".to_string(), ..Default::default() }, Disk { endpoint: "node2:9000".to_string(), state: "offline".to_string(), ..Default::default() }, ], backend: BackendInfo::default(), }; assert_eq!(storage_info.disks.len(), 2); assert_eq!(storage_info.disks[0].endpoint, "node1:9000"); assert_eq!(storage_info.disks[1].state, "offline"); } #[test] fn test_backend_disks_new() { let backend_disks = BackendDisks::new(); assert!(backend_disks.0.is_empty()); } #[test] fn test_backend_disks_sum() { let mut backend_disks = BackendDisks::new(); backend_disks.0.insert("pool1".to_string(), 4); backend_disks.0.insert("pool2".to_string(), 6); backend_disks.0.insert("pool3".to_string(), 2); assert_eq!(backend_disks.sum(), 12); } #[test] fn test_backend_disks_sum_empty() { let backend_disks = BackendDisks::new(); assert_eq!(backend_disks.sum(), 0); } #[test] fn test_backend_info_default() { let backend_info = BackendInfo::default(); assert!(matches!(backend_info.backend_type, BackendByte::Unknown)); assert_eq!(backend_info.online_disks.sum(), 0); assert_eq!(backend_info.offline_disks.sum(), 0); assert!(backend_info.standard_sc_data.is_empty()); assert!(backend_info.standard_sc_parities.is_empty()); assert!(backend_info.standard_sc_parity.is_none()); assert!(backend_info.rr_sc_data.is_empty()); assert!(backend_info.rr_sc_parities.is_empty()); assert!(backend_info.rr_sc_parity.is_none()); assert!(backend_info.total_sets.is_empty()); assert!(backend_info.drives_per_set.is_empty()); } #[test] fn test_backend_info_with_values() { let mut online_disks = BackendDisks::new(); online_disks.0.insert("set1".to_string(), 4); online_disks.0.insert("set2".to_string(), 4); let mut offline_disks = BackendDisks::new(); offline_disks.0.insert("set1".to_string(), 0); offline_disks.0.insert("set2".to_string(), 1); let backend_info = BackendInfo { backend_type: BackendByte::Erasure, online_disks, offline_disks, standard_sc_data: vec![4, 4], standard_sc_parities: vec![2, 2], standard_sc_parity: Some(2), rr_sc_data: vec![2, 2], rr_sc_parities: vec![1, 1], rr_sc_parity: Some(1), total_sets: vec![2], drives_per_set: vec![6, 6], }; assert!(matches!(backend_info.backend_type, BackendByte::Erasure)); assert_eq!(backend_info.online_disks.sum(), 8); assert_eq!(backend_info.offline_disks.sum(), 1); assert_eq!(backend_info.standard_sc_data.len(), 2); assert_eq!(backend_info.standard_sc_parity.unwrap(), 2); assert_eq!(backend_info.total_sets.len(), 1); assert_eq!(backend_info.drives_per_set.len(), 2); } #[test] fn test_mem_stats_default() { let mem_stats = MemStats::default(); assert_eq!(mem_stats.alloc, 0); assert_eq!(mem_stats.total_alloc, 0); assert_eq!(mem_stats.mallocs, 0); assert_eq!(mem_stats.frees, 0); assert_eq!(mem_stats.heap_alloc, 0); } #[test] fn test_mem_stats_with_values() { let mem_stats = MemStats { alloc: 1024000, total_alloc: 5120000, mallocs: 1000, frees: 800, heap_alloc: 2048000, }; assert_eq!(mem_stats.alloc, 1024000); assert_eq!(mem_stats.total_alloc, 5120000); assert_eq!(mem_stats.mallocs, 1000); assert_eq!(mem_stats.frees, 800); assert_eq!(mem_stats.heap_alloc, 2048000); } #[test] fn test_server_properties_default() { let server_props = ServerProperties::default(); assert!(server_props.state.is_empty()); assert!(server_props.endpoint.is_empty()); assert!(server_props.scheme.is_empty()); assert_eq!(server_props.uptime, 0); assert!(server_props.version.is_empty()); assert!(server_props.commit_id.is_empty()); assert!(server_props.network.is_empty()); assert!(server_props.disks.is_empty()); assert_eq!(server_props.pool_number, 0); assert!(server_props.pool_numbers.is_empty()); assert_eq!(server_props.mem_stats.alloc, 0); assert_eq!(server_props.max_procs, 0); assert_eq!(server_props.num_cpu, 0); assert!(server_props.runtime_version.is_empty()); assert!(server_props.rustfs_env_vars.is_empty()); } #[test] fn test_server_properties_with_values() { let mut network = HashMap::new(); network.insert("interface".to_string(), "eth0".to_string()); network.insert("ip".to_string(), "192.168.1.100".to_string()); let mut env_vars = HashMap::new(); env_vars.insert("RUSTFS_ROOT_USER".to_string(), "admin".to_string()); env_vars.insert("RUSTFS_ROOT_PASSWORD".to_string(), "password".to_string()); let server_props = ServerProperties { state: "online".to_string(), endpoint: "http://localhost:9000".to_string(), scheme: "http".to_string(), uptime: 3600, version: "1.0.0".to_string(), commit_id: "abc123def456".to_string(), network, disks: vec![Disk::default()], pool_number: 1, pool_numbers: vec![0, 1], mem_stats: MemStats { alloc: 1024000, total_alloc: 5120000, mallocs: 1000, frees: 800, heap_alloc: 2048000, }, max_procs: 8, num_cpu: 4, runtime_version: "1.70.0".to_string(), rustfs_env_vars: env_vars, }; assert_eq!(server_props.state, "online"); assert_eq!(server_props.endpoint, "http://localhost:9000"); assert_eq!(server_props.uptime, 3600); assert_eq!(server_props.version, "1.0.0"); assert_eq!(server_props.network.len(), 2); assert_eq!(server_props.disks.len(), 1); assert_eq!(server_props.pool_number, 1); assert_eq!(server_props.pool_numbers.len(), 2); assert_eq!(server_props.mem_stats.alloc, 1024000); assert_eq!(server_props.max_procs, 8); assert_eq!(server_props.num_cpu, 4); assert_eq!(server_props.rustfs_env_vars.len(), 2); } #[test] fn test_kms_default() { let kms = Kms::default(); assert!(kms.status.is_none()); assert!(kms.encrypt.is_none()); assert!(kms.decrypt.is_none()); assert!(kms.endpoint.is_none()); assert!(kms.version.is_none()); } #[test] fn test_kms_with_values() { let kms = Kms { status: Some("enabled".to_string()), encrypt: Some("AES256".to_string()), decrypt: Some("AES256".to_string()), endpoint: Some("https://kms.example.com".to_string()), version: Some("1.0".to_string()), }; assert_eq!(kms.status.unwrap(), "enabled"); assert_eq!(kms.encrypt.unwrap(), "AES256"); assert_eq!(kms.decrypt.unwrap(), "AES256"); assert_eq!(kms.endpoint.unwrap(), "https://kms.example.com"); assert_eq!(kms.version.unwrap(), "1.0"); } #[test] fn test_ldap_default() { let ldap = Ldap::default(); assert!(ldap.status.is_none()); } #[test] fn test_ldap_with_values() { let ldap = Ldap { status: Some("enabled".to_string()), }; assert_eq!(ldap.status.unwrap(), "enabled"); } #[test] fn test_status_default() { let status = Status::default(); assert!(status.status.is_none()); } #[test] fn test_status_with_values() { let status = Status { status: Some("active".to_string()), }; assert_eq!(status.status.unwrap(), "active"); } #[test] fn test_services_default() { let services = Services::default(); assert!(services.kms.is_none()); assert!(services.kms_status.is_none()); assert!(services.ldap.is_none()); assert!(services.logger.is_none()); assert!(services.audit.is_none()); assert!(services.notifications.is_none()); } #[test] fn test_services_with_values() { let services = Services { kms: Some(Kms::default()), kms_status: Some(vec![Kms::default()]), ldap: Some(Ldap::default()), logger: Some(vec![HashMap::new()]), audit: Some(vec![HashMap::new()]), notifications: Some(vec![HashMap::new()]), }; assert!(services.kms.is_some()); assert_eq!(services.kms_status.unwrap().len(), 1); assert!(services.ldap.is_some()); assert_eq!(services.logger.unwrap().len(), 1); assert_eq!(services.audit.unwrap().len(), 1); assert_eq!(services.notifications.unwrap().len(), 1); } #[test] fn test_buckets_default() { let buckets = Buckets::default(); assert_eq!(buckets.count, 0); assert!(buckets.error.is_none()); } #[test] fn test_buckets_with_values() { let buckets = Buckets { count: 10, error: Some("Access denied".to_string()), }; assert_eq!(buckets.count, 10); assert_eq!(buckets.error.unwrap(), "Access denied"); } #[test] fn test_objects_default() { let objects = Objects::default(); assert_eq!(objects.count, 0); assert!(objects.error.is_none()); } #[test] fn test_versions_default() { let versions = Versions::default(); assert_eq!(versions.count, 0); assert!(versions.error.is_none()); } #[test] fn test_delete_markers_default() { let delete_markers = DeleteMarkers::default(); assert_eq!(delete_markers.count, 0); assert!(delete_markers.error.is_none()); } #[test] fn test_usage_default() { let usage = Usage::default(); assert_eq!(usage.size, 0); assert!(usage.error.is_none()); } #[test] fn test_erasure_set_info_default() { let erasure_set = ErasureSetInfo::default(); assert_eq!(erasure_set.id, 0); assert_eq!(erasure_set.raw_usage, 0); assert_eq!(erasure_set.raw_capacity, 0); assert_eq!(erasure_set.usage, 0); assert_eq!(erasure_set.objects_count, 0); assert_eq!(erasure_set.versions_count, 0); assert_eq!(erasure_set.delete_markers_count, 0); assert_eq!(erasure_set.heal_disks, 0); } #[test] fn test_erasure_set_info_with_values() { let erasure_set = ErasureSetInfo { id: 1, raw_usage: 1000000000, raw_capacity: 2000000000, usage: 800000000, objects_count: 10000, versions_count: 15000, delete_markers_count: 500, heal_disks: 2, }; assert_eq!(erasure_set.id, 1); assert_eq!(erasure_set.raw_usage, 1000000000); assert_eq!(erasure_set.raw_capacity, 2000000000); assert_eq!(erasure_set.usage, 800000000); assert_eq!(erasure_set.objects_count, 10000); assert_eq!(erasure_set.versions_count, 15000); assert_eq!(erasure_set.delete_markers_count, 500); assert_eq!(erasure_set.heal_disks, 2); } #[test] fn test_backend_type_default() { let backend_type = BackendType::default(); assert!(matches!(backend_type, BackendType::FsType)); } #[test] fn test_backend_type_variants() { let fs_type = BackendType::FsType; let erasure_type = BackendType::ErasureType; assert!(matches!(fs_type, BackendType::FsType)); assert!(matches!(erasure_type, BackendType::ErasureType)); } #[test] fn test_fs_backend_creation() { let fs_backend = FSBackend { backend_type: BackendType::FsType, }; assert!(matches!(fs_backend.backend_type, BackendType::FsType)); } #[test] fn test_erasure_backend_default() { let erasure_backend = ErasureBackend::default(); assert!(matches!(erasure_backend.backend_type, BackendType::FsType)); assert_eq!(erasure_backend.online_disks, 0); assert_eq!(erasure_backend.offline_disks, 0); assert!(erasure_backend.standard_sc_parity.is_none()); assert!(erasure_backend.rr_sc_parity.is_none()); assert!(erasure_backend.total_sets.is_empty()); assert!(erasure_backend.drives_per_set.is_empty()); } #[test] fn test_erasure_backend_with_values() { let erasure_backend = ErasureBackend { backend_type: BackendType::ErasureType, online_disks: 8, offline_disks: 0, standard_sc_parity: Some(2), rr_sc_parity: Some(1), total_sets: vec![2], drives_per_set: vec![4, 4], }; assert!(matches!(erasure_backend.backend_type, BackendType::ErasureType)); assert_eq!(erasure_backend.online_disks, 8); assert_eq!(erasure_backend.offline_disks, 0); assert_eq!(erasure_backend.standard_sc_parity.unwrap(), 2); assert_eq!(erasure_backend.rr_sc_parity.unwrap(), 1); assert_eq!(erasure_backend.total_sets.len(), 1); assert_eq!(erasure_backend.drives_per_set.len(), 2); } #[test] fn test_info_message_creation() { let mut pools = HashMap::new(); let mut pool_sets = HashMap::new(); pool_sets.insert(0, ErasureSetInfo::default()); pools.insert(0, pool_sets); let info_message = InfoMessage { mode: Some("distributed".to_string()), domain: Some(vec!["example.com".to_string()]), region: Some("us-east-1".to_string()), sqs_arn: Some(vec!["arn:aws:sqs:us-east-1:123456789012:test-queue".to_string()]), deployment_id: Some("deployment-123".to_string()), buckets: Some(Buckets { count: 5, error: None }), objects: Some(Objects { count: 1000, error: None, }), versions: Some(Versions { count: 1200, error: None, }), delete_markers: Some(DeleteMarkers { count: 50, error: None }), usage: Some(Usage { size: 1000000000, error: None, }), services: Some(Services::default()), backend: Some(ErasureBackend::default()), servers: Some(vec![ServerProperties::default()]), pools: Some(pools), }; assert_eq!(info_message.mode.unwrap(), "distributed"); assert_eq!(info_message.domain.unwrap().len(), 1); assert_eq!(info_message.region.unwrap(), "us-east-1"); assert_eq!(info_message.sqs_arn.unwrap().len(), 1); assert_eq!(info_message.deployment_id.unwrap(), "deployment-123"); assert_eq!(info_message.buckets.unwrap().count, 5); assert_eq!(info_message.objects.unwrap().count, 1000); assert_eq!(info_message.versions.unwrap().count, 1200); assert_eq!(info_message.delete_markers.unwrap().count, 50); assert_eq!(info_message.usage.unwrap().size, 1000000000); assert!(info_message.services.is_some()); assert_eq!(info_message.servers.unwrap().len(), 1); assert_eq!(info_message.pools.unwrap().len(), 1); } #[test] fn test_serialization_deserialization() { let disk = Disk { endpoint: "http://localhost:9000".to_string(), state: "online".to_string(), total_space: 1000000000, used_space: 500000000, ..Default::default() }; let json = serde_json::to_string(&disk).unwrap(); let deserialized: Disk = serde_json::from_str(&json).unwrap(); assert_eq!(deserialized.endpoint, "http://localhost:9000"); assert_eq!(deserialized.state, "online"); assert_eq!(deserialized.total_space, 1000000000); assert_eq!(deserialized.used_space, 500000000); } #[test] fn test_debug_format_all_structures() { let item_state = ItemState::Online; let disk_metrics = DiskMetrics::default(); let disk = Disk::default(); let healing_disk = HealingDisk::default(); let backend_byte = BackendByte::default(); let storage_info = StorageInfo { disks: vec![], backend: BackendInfo::default(), }; let backend_info = BackendInfo::default(); let mem_stats = MemStats::default(); let server_props = ServerProperties::default(); // Test that all structures can be formatted with Debug assert!(!format!("{item_state:?}").is_empty()); assert!(!format!("{disk_metrics:?}").is_empty()); assert!(!format!("{disk:?}").is_empty()); assert!(!format!("{healing_disk:?}").is_empty()); assert!(!format!("{backend_byte:?}").is_empty()); assert!(!format!("{storage_info:?}").is_empty()); assert!(!format!("{backend_info:?}").is_empty()); assert!(!format!("{mem_stats:?}").is_empty()); assert!(!format!("{server_props:?}").is_empty()); } #[test] fn test_memory_efficiency() { // Test that structures don't use excessive memory assert!(std::mem::size_of::() < 100); assert!(std::mem::size_of::() < 100); assert!(std::mem::size_of::() < 100); assert!(std::mem::size_of::() < 1000); assert!(std::mem::size_of::() < 1000); assert!(std::mem::size_of::() < 1000); assert!(std::mem::size_of::() < 1000); } #[test] fn test_constants() { assert_eq!(ITEM_OFFLINE, "offline"); assert_eq!(ITEM_INITIALIZING, "initializing"); assert_eq!(ITEM_ONLINE, "online"); } }