Skip to main content

rustfs_madmin/
info_commands.rs

1// Copyright 2024 RustFS Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::{collections::HashMap, time::SystemTime};
16
17use serde::{Deserialize, Serialize};
18use time::OffsetDateTime;
19
20use crate::metrics::TimedAction;
21
22#[derive(Debug, PartialEq, Clone, Copy)]
23pub enum ItemState {
24    Offline,
25    Initializing,
26    Online,
27}
28
29impl ItemState {
30    pub fn to_string(&self) -> &str {
31        match self {
32            ItemState::Offline => "offline",
33            ItemState::Initializing => "initializing",
34            ItemState::Online => "online",
35        }
36    }
37
38    pub fn from_string(s: &str) -> Option<ItemState> {
39        match s {
40            "offline" => Some(ItemState::Offline),
41            "initializing" => Some(ItemState::Initializing),
42            "online" => Some(ItemState::Online),
43            _ => None,
44        }
45    }
46}
47
48#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
49pub struct DiskMetrics {
50    pub last_minute: HashMap<String, TimedAction>,
51    pub api_calls: HashMap<String, u64>,
52    pub total_waiting: u32,
53    pub total_errors_availability: u64,
54    pub total_errors_timeout: u64,
55    pub total_writes: u64,
56    pub total_deletes: u64,
57}
58
59#[derive(Serialize, Deserialize, Debug, Default, Clone)]
60pub struct Disk {
61    pub endpoint: String,
62    #[serde(rename = "rootDisk")]
63    pub root_disk: bool,
64    #[serde(rename = "path")]
65    pub drive_path: String,
66    pub healing: bool,
67    pub scanning: bool,
68    pub state: String,
69    pub uuid: String,
70    pub major: u32,
71    pub minor: u32,
72    pub model: Option<String>,
73    #[serde(rename = "totalspace")]
74    pub total_space: u64,
75    #[serde(rename = "usedspace")]
76    pub used_space: u64,
77    #[serde(rename = "availspace")]
78    pub available_space: u64,
79    #[serde(rename = "readthroughput")]
80    pub read_throughput: f64,
81    #[serde(rename = "writethroughput")]
82    pub write_throughput: f64,
83    #[serde(rename = "readlatency")]
84    pub read_latency: f64,
85    #[serde(rename = "writelatency")]
86    pub write_latency: f64,
87    pub utilization: f64,
88    pub metrics: Option<DiskMetrics>,
89    pub heal_info: Option<HealingDisk>,
90    pub used_inodes: u64,
91    pub free_inodes: u64,
92    pub local: bool,
93    pub pool_index: i32,
94    pub set_index: i32,
95    pub disk_index: i32,
96}
97
98#[derive(Clone, Debug, Default, Serialize, Deserialize)]
99pub struct HealingDisk {
100    pub id: String,
101    pub heal_id: String,
102    pub pool_index: Option<usize>,
103    pub set_index: Option<usize>,
104    pub disk_index: Option<usize>,
105    pub endpoint: String,
106    pub path: String,
107    pub started: Option<OffsetDateTime>,
108    pub last_update: Option<SystemTime>,
109    pub retry_attempts: u64,
110    pub objects_total_count: u64,
111    pub objects_total_size: u64,
112    pub items_healed: u64,
113    pub items_failed: u64,
114    pub item_skipped: u64,
115    pub bytes_done: u64,
116    pub bytes_failed: u64,
117    pub bytes_skipped: u64,
118    pub objects_healed: u64,
119    pub objects_failed: u64,
120    pub bucket: String,
121    pub object: String,
122    pub queue_buckets: Vec<String>,
123    pub healed_buckets: Vec<String>,
124    pub finished: bool,
125}
126
127#[derive(Debug, Default, Serialize, Deserialize)]
128pub enum BackendByte {
129    #[default]
130    Unknown,
131    FS,
132    Erasure,
133}
134
135#[derive(Debug, Default, Serialize, Deserialize)]
136pub struct StorageInfo {
137    pub disks: Vec<Disk>,
138    pub backend: BackendInfo,
139}
140
141#[derive(Debug, Default, Serialize, Deserialize)]
142pub struct BackendDisks(pub HashMap<String, usize>);
143
144impl BackendDisks {
145    pub fn new() -> Self {
146        Self(HashMap::new())
147    }
148    pub fn sum(&self) -> usize {
149        self.0.values().sum()
150    }
151}
152
153#[derive(Debug, Default, Serialize, Deserialize)]
154#[serde(rename_all = "PascalCase", default)]
155pub struct BackendInfo {
156    pub backend_type: BackendByte,
157    pub online_disks: BackendDisks,
158    pub offline_disks: BackendDisks,
159    #[serde(rename = "StandardSCData")]
160    pub standard_sc_data: Vec<usize>,
161    #[serde(rename = "StandardSCParities")]
162    pub standard_sc_parities: Vec<usize>,
163    #[serde(rename = "StandardSCParity")]
164    pub standard_sc_parity: Option<usize>,
165    #[serde(rename = "RRSCData")]
166    pub rr_sc_data: Vec<usize>,
167    #[serde(rename = "RRSCParities")]
168    pub rr_sc_parities: Vec<usize>,
169    #[serde(rename = "RRSCParity")]
170    pub rr_sc_parity: Option<usize>,
171    pub total_sets: Vec<usize>,
172    pub drives_per_set: Vec<usize>,
173}
174
175pub const ITEM_OFFLINE: &str = "offline";
176pub const ITEM_INITIALIZING: &str = "initializing";
177pub const ITEM_ONLINE: &str = "online";
178
179#[derive(Debug, Default, Serialize, Deserialize)]
180pub struct MemStats {
181    pub alloc: u64,
182    pub total_alloc: u64,
183    pub mallocs: u64,
184    pub frees: u64,
185    pub heap_alloc: u64,
186}
187
188#[derive(Debug, Default, Serialize, Deserialize)]
189pub struct ServerProperties {
190    pub state: String,
191    pub endpoint: String,
192    pub scheme: String,
193    pub uptime: u64,
194    pub version: String,
195    #[serde(rename = "commitID")]
196    pub commit_id: String,
197    pub network: HashMap<String, String>,
198    #[serde(rename = "drives")]
199    pub disks: Vec<Disk>,
200    #[serde(rename = "poolNumber")]
201    pub pool_number: i32,
202    #[serde(rename = "poolNumbers")]
203    pub pool_numbers: Vec<i32>,
204    pub mem_stats: MemStats,
205    pub max_procs: u64,
206    pub num_cpu: u64,
207    pub runtime_version: String,
208    pub rustfs_env_vars: HashMap<String, String>,
209}
210
211#[derive(Serialize, Deserialize, Debug, Default)]
212pub struct Kms {
213    pub status: Option<String>,
214    pub encrypt: Option<String>,
215    pub decrypt: Option<String>,
216    pub endpoint: Option<String>,
217    pub version: Option<String>,
218}
219
220#[derive(Serialize, Deserialize, Debug, Default)]
221pub struct Ldap {
222    pub status: Option<String>,
223}
224
225#[derive(Serialize, Deserialize, Debug, Default)]
226pub struct Status {
227    pub status: Option<String>,
228}
229
230pub type Audit = HashMap<String, Status>;
231
232pub type Logger = HashMap<String, Status>;
233
234pub type TargetIDStatus = HashMap<String, Status>;
235
236#[derive(Serialize, Deserialize, Default, Debug)]
237pub struct Services {
238    pub kms: Option<Kms>, // deprecated july 2023
239    #[serde(rename = "kmsStatus")]
240    pub kms_status: Option<Vec<Kms>>,
241    pub ldap: Option<Ldap>,
242    pub logger: Option<Vec<Logger>>,
243    pub audit: Option<Vec<Audit>>,
244    pub notifications: Option<Vec<HashMap<String, Vec<TargetIDStatus>>>>,
245}
246
247#[derive(Serialize, Deserialize, Debug, Default)]
248pub struct Buckets {
249    pub count: u64,
250    pub error: Option<String>,
251}
252
253#[derive(Serialize, Deserialize, Debug, Default)]
254pub struct Objects {
255    pub count: u64,
256    pub error: Option<String>,
257}
258
259#[derive(Serialize, Deserialize, Debug, Default)]
260pub struct Versions {
261    pub count: u64,
262    pub error: Option<String>,
263}
264
265#[derive(Serialize, Deserialize, Debug, Default)]
266pub struct DeleteMarkers {
267    pub count: u64,
268    pub error: Option<String>,
269}
270
271#[derive(Serialize, Deserialize, Debug, Default)]
272pub struct Usage {
273    pub size: u64,
274    pub error: Option<String>,
275}
276
277#[derive(Serialize, Deserialize, Debug, Default)]
278pub struct ErasureSetInfo {
279    pub id: i32,
280    #[serde(rename = "rawUsage")]
281    pub raw_usage: u64,
282    #[serde(rename = "rawCapacity")]
283    pub raw_capacity: u64,
284    pub usage: u64,
285    #[serde(rename = "objectsCount")]
286    pub objects_count: u64,
287    #[serde(rename = "versionsCount")]
288    pub versions_count: u64,
289    #[serde(rename = "deleteMarkersCount")]
290    pub delete_markers_count: u64,
291    #[serde(rename = "healDisks")]
292    pub heal_disks: i32,
293}
294
295#[derive(Serialize, Deserialize, Debug, Default)]
296pub enum BackendType {
297    #[default]
298    #[serde(rename = "FS")]
299    FsType,
300    #[serde(rename = "Erasure")]
301    ErasureType,
302}
303
304#[derive(Serialize, Deserialize)]
305pub struct FSBackend {
306    #[serde(rename = "backendType")]
307    pub backend_type: BackendType,
308}
309
310#[derive(Serialize, Deserialize, Debug, Default)]
311pub struct ErasureBackend {
312    #[serde(rename = "backendType")]
313    pub backend_type: BackendType,
314    #[serde(rename = "onlineDisks")]
315    pub online_disks: usize,
316    #[serde(rename = "offlineDisks")]
317    pub offline_disks: usize,
318    #[serde(rename = "standardSCParity")]
319    pub standard_sc_parity: Option<usize>,
320    #[serde(rename = "rrSCParity")]
321    pub rr_sc_parity: Option<usize>,
322    #[serde(rename = "totalSets")]
323    pub total_sets: Vec<usize>,
324    #[serde(rename = "totalDrivesPerSet")]
325    pub drives_per_set: Vec<usize>,
326}
327
328#[derive(Serialize, Deserialize)]
329pub struct InfoMessage {
330    pub mode: Option<String>,
331    pub domain: Option<Vec<String>>,
332    pub region: Option<String>,
333    #[serde(rename = "sqsARN")]
334    pub sqs_arn: Option<Vec<String>>,
335    #[serde(rename = "deploymentID")]
336    pub deployment_id: Option<String>,
337    pub buckets: Option<Buckets>,
338    pub objects: Option<Objects>,
339    pub versions: Option<Versions>,
340    #[serde(rename = "deletemarkers")]
341    pub delete_markers: Option<DeleteMarkers>,
342    pub usage: Option<Usage>,
343    pub services: Option<Services>,
344    pub backend: Option<ErasureBackend>,
345    pub servers: Option<Vec<ServerProperties>>,
346    pub pools: Option<std::collections::HashMap<i32, std::collections::HashMap<i32, ErasureSetInfo>>>,
347}
348
349#[cfg(test)]
350mod tests {
351    use super::*;
352    use serde_json;
353    use std::collections::HashMap;
354    use time::OffsetDateTime;
355
356    #[test]
357    fn test_item_state_to_string() {
358        assert_eq!(ItemState::Offline.to_string(), ITEM_OFFLINE);
359        assert_eq!(ItemState::Initializing.to_string(), ITEM_INITIALIZING);
360        assert_eq!(ItemState::Online.to_string(), ITEM_ONLINE);
361    }
362
363    #[test]
364    fn test_item_state_from_string_valid() {
365        assert_eq!(ItemState::from_string(ITEM_OFFLINE), Some(ItemState::Offline));
366        assert_eq!(ItemState::from_string(ITEM_INITIALIZING), Some(ItemState::Initializing));
367        assert_eq!(ItemState::from_string(ITEM_ONLINE), Some(ItemState::Online));
368    }
369
370    #[test]
371    fn test_item_state_from_string_invalid() {
372        assert_eq!(ItemState::from_string("invalid"), None);
373        assert_eq!(ItemState::from_string(""), None);
374        assert_eq!(ItemState::from_string("OFFLINE"), None); // Case sensitive
375    }
376
377    #[test]
378    fn test_disk_metrics_default() {
379        let metrics = DiskMetrics::default();
380        assert!(metrics.last_minute.is_empty());
381        assert!(metrics.api_calls.is_empty());
382        assert_eq!(metrics.total_waiting, 0);
383        assert_eq!(metrics.total_errors_availability, 0);
384        assert_eq!(metrics.total_errors_timeout, 0);
385        assert_eq!(metrics.total_writes, 0);
386        assert_eq!(metrics.total_deletes, 0);
387    }
388
389    #[test]
390    fn test_disk_metrics_with_values() {
391        let mut last_minute = HashMap::new();
392        last_minute.insert("read".to_string(), TimedAction::default());
393
394        let mut api_calls = HashMap::new();
395        api_calls.insert("GET".to_string(), 100);
396        api_calls.insert("PUT".to_string(), 50);
397
398        let metrics = DiskMetrics {
399            last_minute,
400            api_calls,
401            total_waiting: 5,
402            total_errors_availability: 2,
403            total_errors_timeout: 1,
404            total_writes: 1000,
405            total_deletes: 50,
406        };
407
408        assert_eq!(metrics.last_minute.len(), 1);
409        assert_eq!(metrics.api_calls.len(), 2);
410        assert_eq!(metrics.total_waiting, 5);
411        assert_eq!(metrics.total_writes, 1000);
412        assert_eq!(metrics.total_deletes, 50);
413    }
414
415    #[test]
416    fn test_disk_default() {
417        let disk = Disk::default();
418        assert!(disk.endpoint.is_empty());
419        assert!(!disk.root_disk);
420        assert!(disk.drive_path.is_empty());
421        assert!(!disk.healing);
422        assert!(!disk.scanning);
423        assert!(disk.state.is_empty());
424        assert!(disk.uuid.is_empty());
425        assert_eq!(disk.major, 0);
426        assert_eq!(disk.minor, 0);
427        assert!(disk.model.is_none());
428        assert_eq!(disk.total_space, 0);
429        assert_eq!(disk.used_space, 0);
430        assert_eq!(disk.available_space, 0);
431        assert_eq!(disk.read_throughput, 0.0);
432        assert_eq!(disk.write_throughput, 0.0);
433        assert_eq!(disk.read_latency, 0.0);
434        assert_eq!(disk.write_latency, 0.0);
435        assert_eq!(disk.utilization, 0.0);
436        assert!(disk.metrics.is_none());
437        assert!(disk.heal_info.is_none());
438        assert_eq!(disk.used_inodes, 0);
439        assert_eq!(disk.free_inodes, 0);
440        assert!(!disk.local);
441        assert_eq!(disk.pool_index, 0);
442        assert_eq!(disk.set_index, 0);
443        assert_eq!(disk.disk_index, 0);
444    }
445
446    #[test]
447    fn test_disk_with_values() {
448        let disk = Disk {
449            endpoint: "http://localhost:9000".to_string(),
450            root_disk: true,
451            drive_path: "/data/disk1".to_string(),
452            healing: false,
453            scanning: true,
454            state: "online".to_string(),
455            uuid: "12345678-1234-1234-1234-123456789abc".to_string(),
456            major: 8,
457            minor: 1,
458            model: Some("Samsung SSD 980".to_string()),
459            total_space: 1000000000000,
460            used_space: 500000000000,
461            available_space: 500000000000,
462            read_throughput: 100.5,
463            write_throughput: 80.3,
464            read_latency: 5.2,
465            write_latency: 7.8,
466            utilization: 50.0,
467            metrics: Some(DiskMetrics::default()),
468            heal_info: None,
469            used_inodes: 1000000,
470            free_inodes: 9000000,
471            local: true,
472            pool_index: 0,
473            set_index: 1,
474            disk_index: 2,
475        };
476
477        assert_eq!(disk.endpoint, "http://localhost:9000");
478        assert!(disk.root_disk);
479        assert_eq!(disk.drive_path, "/data/disk1");
480        assert!(disk.scanning);
481        assert_eq!(disk.state, "online");
482        assert_eq!(disk.major, 8);
483        assert_eq!(disk.minor, 1);
484        assert_eq!(disk.model.unwrap(), "Samsung SSD 980");
485        assert_eq!(disk.total_space, 1000000000000);
486        assert_eq!(disk.utilization, 50.0);
487        assert!(disk.metrics.is_some());
488        assert!(disk.local);
489    }
490
491    #[test]
492    fn test_healing_disk_default() {
493        let healing_disk = HealingDisk::default();
494        assert!(healing_disk.id.is_empty());
495        assert!(healing_disk.heal_id.is_empty());
496        assert!(healing_disk.pool_index.is_none());
497        assert!(healing_disk.set_index.is_none());
498        assert!(healing_disk.disk_index.is_none());
499        assert!(healing_disk.endpoint.is_empty());
500        assert!(healing_disk.path.is_empty());
501        assert!(healing_disk.started.is_none());
502        assert!(healing_disk.last_update.is_none());
503        assert_eq!(healing_disk.retry_attempts, 0);
504        assert_eq!(healing_disk.objects_total_count, 0);
505        assert_eq!(healing_disk.objects_total_size, 0);
506        assert_eq!(healing_disk.items_healed, 0);
507        assert_eq!(healing_disk.items_failed, 0);
508        assert_eq!(healing_disk.item_skipped, 0);
509        assert_eq!(healing_disk.bytes_done, 0);
510        assert_eq!(healing_disk.bytes_failed, 0);
511        assert_eq!(healing_disk.bytes_skipped, 0);
512        assert_eq!(healing_disk.objects_healed, 0);
513        assert_eq!(healing_disk.objects_failed, 0);
514        assert!(healing_disk.bucket.is_empty());
515        assert!(healing_disk.object.is_empty());
516        assert!(healing_disk.queue_buckets.is_empty());
517        assert!(healing_disk.healed_buckets.is_empty());
518        assert!(!healing_disk.finished);
519    }
520
521    #[test]
522    fn test_healing_disk_with_values() {
523        let now = OffsetDateTime::now_utc();
524        let system_time = std::time::SystemTime::now();
525
526        let healing_disk = HealingDisk {
527            id: "heal-001".to_string(),
528            heal_id: "heal-session-123".to_string(),
529            pool_index: Some(0),
530            set_index: Some(1),
531            disk_index: Some(2),
532            endpoint: "http://node1:9000".to_string(),
533            path: "/data/disk1".to_string(),
534            started: Some(now),
535            last_update: Some(system_time),
536            retry_attempts: 3,
537            objects_total_count: 10000,
538            objects_total_size: 1000000000,
539            items_healed: 8000,
540            items_failed: 100,
541            item_skipped: 50,
542            bytes_done: 800000000,
543            bytes_failed: 10000000,
544            bytes_skipped: 5000000,
545            objects_healed: 7900,
546            objects_failed: 100,
547            bucket: "test-bucket".to_string(),
548            object: "test-object".to_string(),
549            queue_buckets: vec!["bucket1".to_string(), "bucket2".to_string()],
550            healed_buckets: vec!["bucket3".to_string()],
551            finished: false,
552        };
553
554        assert_eq!(healing_disk.id, "heal-001");
555        assert_eq!(healing_disk.heal_id, "heal-session-123");
556        assert_eq!(healing_disk.pool_index.unwrap(), 0);
557        assert_eq!(healing_disk.set_index.unwrap(), 1);
558        assert_eq!(healing_disk.disk_index.unwrap(), 2);
559        assert_eq!(healing_disk.retry_attempts, 3);
560        assert_eq!(healing_disk.objects_total_count, 10000);
561        assert_eq!(healing_disk.items_healed, 8000);
562        assert_eq!(healing_disk.queue_buckets.len(), 2);
563        assert_eq!(healing_disk.healed_buckets.len(), 1);
564        assert!(!healing_disk.finished);
565    }
566
567    #[test]
568    fn test_backend_byte_default() {
569        let backend = BackendByte::default();
570        assert!(matches!(backend, BackendByte::Unknown));
571    }
572
573    #[test]
574    fn test_backend_byte_variants() {
575        let unknown = BackendByte::Unknown;
576        let fs = BackendByte::FS;
577        let erasure = BackendByte::Erasure;
578
579        // Test that all variants can be created
580        assert!(matches!(unknown, BackendByte::Unknown));
581        assert!(matches!(fs, BackendByte::FS));
582        assert!(matches!(erasure, BackendByte::Erasure));
583    }
584
585    #[test]
586    fn test_storage_info_creation() {
587        let storage_info = StorageInfo {
588            disks: vec![
589                Disk {
590                    endpoint: "node1:9000".to_string(),
591                    state: "online".to_string(),
592                    ..Default::default()
593                },
594                Disk {
595                    endpoint: "node2:9000".to_string(),
596                    state: "offline".to_string(),
597                    ..Default::default()
598                },
599            ],
600            backend: BackendInfo::default(),
601        };
602
603        assert_eq!(storage_info.disks.len(), 2);
604        assert_eq!(storage_info.disks[0].endpoint, "node1:9000");
605        assert_eq!(storage_info.disks[1].state, "offline");
606    }
607
608    #[test]
609    fn test_backend_disks_new() {
610        let backend_disks = BackendDisks::new();
611        assert!(backend_disks.0.is_empty());
612    }
613
614    #[test]
615    fn test_backend_disks_sum() {
616        let mut backend_disks = BackendDisks::new();
617        backend_disks.0.insert("pool1".to_string(), 4);
618        backend_disks.0.insert("pool2".to_string(), 6);
619        backend_disks.0.insert("pool3".to_string(), 2);
620
621        assert_eq!(backend_disks.sum(), 12);
622    }
623
624    #[test]
625    fn test_backend_disks_sum_empty() {
626        let backend_disks = BackendDisks::new();
627        assert_eq!(backend_disks.sum(), 0);
628    }
629
630    #[test]
631    fn test_backend_info_default() {
632        let backend_info = BackendInfo::default();
633        assert!(matches!(backend_info.backend_type, BackendByte::Unknown));
634        assert_eq!(backend_info.online_disks.sum(), 0);
635        assert_eq!(backend_info.offline_disks.sum(), 0);
636        assert!(backend_info.standard_sc_data.is_empty());
637        assert!(backend_info.standard_sc_parities.is_empty());
638        assert!(backend_info.standard_sc_parity.is_none());
639        assert!(backend_info.rr_sc_data.is_empty());
640        assert!(backend_info.rr_sc_parities.is_empty());
641        assert!(backend_info.rr_sc_parity.is_none());
642        assert!(backend_info.total_sets.is_empty());
643        assert!(backend_info.drives_per_set.is_empty());
644    }
645
646    #[test]
647    fn test_backend_info_with_values() {
648        let mut online_disks = BackendDisks::new();
649        online_disks.0.insert("set1".to_string(), 4);
650        online_disks.0.insert("set2".to_string(), 4);
651
652        let mut offline_disks = BackendDisks::new();
653        offline_disks.0.insert("set1".to_string(), 0);
654        offline_disks.0.insert("set2".to_string(), 1);
655
656        let backend_info = BackendInfo {
657            backend_type: BackendByte::Erasure,
658            online_disks,
659            offline_disks,
660            standard_sc_data: vec![4, 4],
661            standard_sc_parities: vec![2, 2],
662            standard_sc_parity: Some(2),
663            rr_sc_data: vec![2, 2],
664            rr_sc_parities: vec![1, 1],
665            rr_sc_parity: Some(1),
666            total_sets: vec![2],
667            drives_per_set: vec![6, 6],
668        };
669
670        assert!(matches!(backend_info.backend_type, BackendByte::Erasure));
671        assert_eq!(backend_info.online_disks.sum(), 8);
672        assert_eq!(backend_info.offline_disks.sum(), 1);
673        assert_eq!(backend_info.standard_sc_data.len(), 2);
674        assert_eq!(backend_info.standard_sc_parity.unwrap(), 2);
675        assert_eq!(backend_info.total_sets.len(), 1);
676        assert_eq!(backend_info.drives_per_set.len(), 2);
677    }
678
679    #[test]
680    fn test_mem_stats_default() {
681        let mem_stats = MemStats::default();
682        assert_eq!(mem_stats.alloc, 0);
683        assert_eq!(mem_stats.total_alloc, 0);
684        assert_eq!(mem_stats.mallocs, 0);
685        assert_eq!(mem_stats.frees, 0);
686        assert_eq!(mem_stats.heap_alloc, 0);
687    }
688
689    #[test]
690    fn test_mem_stats_with_values() {
691        let mem_stats = MemStats {
692            alloc: 1024000,
693            total_alloc: 5120000,
694            mallocs: 1000,
695            frees: 800,
696            heap_alloc: 2048000,
697        };
698
699        assert_eq!(mem_stats.alloc, 1024000);
700        assert_eq!(mem_stats.total_alloc, 5120000);
701        assert_eq!(mem_stats.mallocs, 1000);
702        assert_eq!(mem_stats.frees, 800);
703        assert_eq!(mem_stats.heap_alloc, 2048000);
704    }
705
706    #[test]
707    fn test_server_properties_default() {
708        let server_props = ServerProperties::default();
709        assert!(server_props.state.is_empty());
710        assert!(server_props.endpoint.is_empty());
711        assert!(server_props.scheme.is_empty());
712        assert_eq!(server_props.uptime, 0);
713        assert!(server_props.version.is_empty());
714        assert!(server_props.commit_id.is_empty());
715        assert!(server_props.network.is_empty());
716        assert!(server_props.disks.is_empty());
717        assert_eq!(server_props.pool_number, 0);
718        assert!(server_props.pool_numbers.is_empty());
719        assert_eq!(server_props.mem_stats.alloc, 0);
720        assert_eq!(server_props.max_procs, 0);
721        assert_eq!(server_props.num_cpu, 0);
722        assert!(server_props.runtime_version.is_empty());
723        assert!(server_props.rustfs_env_vars.is_empty());
724    }
725
726    #[test]
727    fn test_server_properties_with_values() {
728        let mut network = HashMap::new();
729        network.insert("interface".to_string(), "eth0".to_string());
730        network.insert("ip".to_string(), "192.168.1.100".to_string());
731
732        let mut env_vars = HashMap::new();
733        env_vars.insert("RUSTFS_ROOT_USER".to_string(), "admin".to_string());
734        env_vars.insert("RUSTFS_ROOT_PASSWORD".to_string(), "password".to_string());
735
736        let server_props = ServerProperties {
737            state: "online".to_string(),
738            endpoint: "http://localhost:9000".to_string(),
739            scheme: "http".to_string(),
740            uptime: 3600,
741            version: "1.0.0".to_string(),
742            commit_id: "abc123def456".to_string(),
743            network,
744            disks: vec![Disk::default()],
745            pool_number: 1,
746            pool_numbers: vec![0, 1],
747            mem_stats: MemStats {
748                alloc: 1024000,
749                total_alloc: 5120000,
750                mallocs: 1000,
751                frees: 800,
752                heap_alloc: 2048000,
753            },
754            max_procs: 8,
755            num_cpu: 4,
756            runtime_version: "1.70.0".to_string(),
757            rustfs_env_vars: env_vars,
758        };
759
760        assert_eq!(server_props.state, "online");
761        assert_eq!(server_props.endpoint, "http://localhost:9000");
762        assert_eq!(server_props.uptime, 3600);
763        assert_eq!(server_props.version, "1.0.0");
764        assert_eq!(server_props.network.len(), 2);
765        assert_eq!(server_props.disks.len(), 1);
766        assert_eq!(server_props.pool_number, 1);
767        assert_eq!(server_props.pool_numbers.len(), 2);
768        assert_eq!(server_props.mem_stats.alloc, 1024000);
769        assert_eq!(server_props.max_procs, 8);
770        assert_eq!(server_props.num_cpu, 4);
771        assert_eq!(server_props.rustfs_env_vars.len(), 2);
772    }
773
774    #[test]
775    fn test_kms_default() {
776        let kms = Kms::default();
777        assert!(kms.status.is_none());
778        assert!(kms.encrypt.is_none());
779        assert!(kms.decrypt.is_none());
780        assert!(kms.endpoint.is_none());
781        assert!(kms.version.is_none());
782    }
783
784    #[test]
785    fn test_kms_with_values() {
786        let kms = Kms {
787            status: Some("enabled".to_string()),
788            encrypt: Some("AES256".to_string()),
789            decrypt: Some("AES256".to_string()),
790            endpoint: Some("https://kms.example.com".to_string()),
791            version: Some("1.0".to_string()),
792        };
793
794        assert_eq!(kms.status.unwrap(), "enabled");
795        assert_eq!(kms.encrypt.unwrap(), "AES256");
796        assert_eq!(kms.decrypt.unwrap(), "AES256");
797        assert_eq!(kms.endpoint.unwrap(), "https://kms.example.com");
798        assert_eq!(kms.version.unwrap(), "1.0");
799    }
800
801    #[test]
802    fn test_ldap_default() {
803        let ldap = Ldap::default();
804        assert!(ldap.status.is_none());
805    }
806
807    #[test]
808    fn test_ldap_with_values() {
809        let ldap = Ldap {
810            status: Some("enabled".to_string()),
811        };
812
813        assert_eq!(ldap.status.unwrap(), "enabled");
814    }
815
816    #[test]
817    fn test_status_default() {
818        let status = Status::default();
819        assert!(status.status.is_none());
820    }
821
822    #[test]
823    fn test_status_with_values() {
824        let status = Status {
825            status: Some("active".to_string()),
826        };
827
828        assert_eq!(status.status.unwrap(), "active");
829    }
830
831    #[test]
832    fn test_services_default() {
833        let services = Services::default();
834        assert!(services.kms.is_none());
835        assert!(services.kms_status.is_none());
836        assert!(services.ldap.is_none());
837        assert!(services.logger.is_none());
838        assert!(services.audit.is_none());
839        assert!(services.notifications.is_none());
840    }
841
842    #[test]
843    fn test_services_with_values() {
844        let services = Services {
845            kms: Some(Kms::default()),
846            kms_status: Some(vec![Kms::default()]),
847            ldap: Some(Ldap::default()),
848            logger: Some(vec![HashMap::new()]),
849            audit: Some(vec![HashMap::new()]),
850            notifications: Some(vec![HashMap::new()]),
851        };
852
853        assert!(services.kms.is_some());
854        assert_eq!(services.kms_status.unwrap().len(), 1);
855        assert!(services.ldap.is_some());
856        assert_eq!(services.logger.unwrap().len(), 1);
857        assert_eq!(services.audit.unwrap().len(), 1);
858        assert_eq!(services.notifications.unwrap().len(), 1);
859    }
860
861    #[test]
862    fn test_buckets_default() {
863        let buckets = Buckets::default();
864        assert_eq!(buckets.count, 0);
865        assert!(buckets.error.is_none());
866    }
867
868    #[test]
869    fn test_buckets_with_values() {
870        let buckets = Buckets {
871            count: 10,
872            error: Some("Access denied".to_string()),
873        };
874
875        assert_eq!(buckets.count, 10);
876        assert_eq!(buckets.error.unwrap(), "Access denied");
877    }
878
879    #[test]
880    fn test_objects_default() {
881        let objects = Objects::default();
882        assert_eq!(objects.count, 0);
883        assert!(objects.error.is_none());
884    }
885
886    #[test]
887    fn test_versions_default() {
888        let versions = Versions::default();
889        assert_eq!(versions.count, 0);
890        assert!(versions.error.is_none());
891    }
892
893    #[test]
894    fn test_delete_markers_default() {
895        let delete_markers = DeleteMarkers::default();
896        assert_eq!(delete_markers.count, 0);
897        assert!(delete_markers.error.is_none());
898    }
899
900    #[test]
901    fn test_usage_default() {
902        let usage = Usage::default();
903        assert_eq!(usage.size, 0);
904        assert!(usage.error.is_none());
905    }
906
907    #[test]
908    fn test_erasure_set_info_default() {
909        let erasure_set = ErasureSetInfo::default();
910        assert_eq!(erasure_set.id, 0);
911        assert_eq!(erasure_set.raw_usage, 0);
912        assert_eq!(erasure_set.raw_capacity, 0);
913        assert_eq!(erasure_set.usage, 0);
914        assert_eq!(erasure_set.objects_count, 0);
915        assert_eq!(erasure_set.versions_count, 0);
916        assert_eq!(erasure_set.delete_markers_count, 0);
917        assert_eq!(erasure_set.heal_disks, 0);
918    }
919
920    #[test]
921    fn test_erasure_set_info_with_values() {
922        let erasure_set = ErasureSetInfo {
923            id: 1,
924            raw_usage: 1000000000,
925            raw_capacity: 2000000000,
926            usage: 800000000,
927            objects_count: 10000,
928            versions_count: 15000,
929            delete_markers_count: 500,
930            heal_disks: 2,
931        };
932
933        assert_eq!(erasure_set.id, 1);
934        assert_eq!(erasure_set.raw_usage, 1000000000);
935        assert_eq!(erasure_set.raw_capacity, 2000000000);
936        assert_eq!(erasure_set.usage, 800000000);
937        assert_eq!(erasure_set.objects_count, 10000);
938        assert_eq!(erasure_set.versions_count, 15000);
939        assert_eq!(erasure_set.delete_markers_count, 500);
940        assert_eq!(erasure_set.heal_disks, 2);
941    }
942
943    #[test]
944    fn test_backend_type_default() {
945        let backend_type = BackendType::default();
946        assert!(matches!(backend_type, BackendType::FsType));
947    }
948
949    #[test]
950    fn test_backend_type_variants() {
951        let fs_type = BackendType::FsType;
952        let erasure_type = BackendType::ErasureType;
953
954        assert!(matches!(fs_type, BackendType::FsType));
955        assert!(matches!(erasure_type, BackendType::ErasureType));
956    }
957
958    #[test]
959    fn test_fs_backend_creation() {
960        let fs_backend = FSBackend {
961            backend_type: BackendType::FsType,
962        };
963
964        assert!(matches!(fs_backend.backend_type, BackendType::FsType));
965    }
966
967    #[test]
968    fn test_erasure_backend_default() {
969        let erasure_backend = ErasureBackend::default();
970        assert!(matches!(erasure_backend.backend_type, BackendType::FsType));
971        assert_eq!(erasure_backend.online_disks, 0);
972        assert_eq!(erasure_backend.offline_disks, 0);
973        assert!(erasure_backend.standard_sc_parity.is_none());
974        assert!(erasure_backend.rr_sc_parity.is_none());
975        assert!(erasure_backend.total_sets.is_empty());
976        assert!(erasure_backend.drives_per_set.is_empty());
977    }
978
979    #[test]
980    fn test_erasure_backend_with_values() {
981        let erasure_backend = ErasureBackend {
982            backend_type: BackendType::ErasureType,
983            online_disks: 8,
984            offline_disks: 0,
985            standard_sc_parity: Some(2),
986            rr_sc_parity: Some(1),
987            total_sets: vec![2],
988            drives_per_set: vec![4, 4],
989        };
990
991        assert!(matches!(erasure_backend.backend_type, BackendType::ErasureType));
992        assert_eq!(erasure_backend.online_disks, 8);
993        assert_eq!(erasure_backend.offline_disks, 0);
994        assert_eq!(erasure_backend.standard_sc_parity.unwrap(), 2);
995        assert_eq!(erasure_backend.rr_sc_parity.unwrap(), 1);
996        assert_eq!(erasure_backend.total_sets.len(), 1);
997        assert_eq!(erasure_backend.drives_per_set.len(), 2);
998    }
999
1000    #[test]
1001    fn test_info_message_creation() {
1002        let mut pools = HashMap::new();
1003        let mut pool_sets = HashMap::new();
1004        pool_sets.insert(0, ErasureSetInfo::default());
1005        pools.insert(0, pool_sets);
1006
1007        let info_message = InfoMessage {
1008            mode: Some("distributed".to_string()),
1009            domain: Some(vec!["example.com".to_string()]),
1010            region: Some("us-east-1".to_string()),
1011            sqs_arn: Some(vec!["arn:aws:sqs:us-east-1:123456789012:test-queue".to_string()]),
1012            deployment_id: Some("deployment-123".to_string()),
1013            buckets: Some(Buckets { count: 5, error: None }),
1014            objects: Some(Objects {
1015                count: 1000,
1016                error: None,
1017            }),
1018            versions: Some(Versions {
1019                count: 1200,
1020                error: None,
1021            }),
1022            delete_markers: Some(DeleteMarkers { count: 50, error: None }),
1023            usage: Some(Usage {
1024                size: 1000000000,
1025                error: None,
1026            }),
1027            services: Some(Services::default()),
1028            backend: Some(ErasureBackend::default()),
1029            servers: Some(vec![ServerProperties::default()]),
1030            pools: Some(pools),
1031        };
1032
1033        assert_eq!(info_message.mode.unwrap(), "distributed");
1034        assert_eq!(info_message.domain.unwrap().len(), 1);
1035        assert_eq!(info_message.region.unwrap(), "us-east-1");
1036        assert_eq!(info_message.sqs_arn.unwrap().len(), 1);
1037        assert_eq!(info_message.deployment_id.unwrap(), "deployment-123");
1038        assert_eq!(info_message.buckets.unwrap().count, 5);
1039        assert_eq!(info_message.objects.unwrap().count, 1000);
1040        assert_eq!(info_message.versions.unwrap().count, 1200);
1041        assert_eq!(info_message.delete_markers.unwrap().count, 50);
1042        assert_eq!(info_message.usage.unwrap().size, 1000000000);
1043        assert!(info_message.services.is_some());
1044        assert_eq!(info_message.servers.unwrap().len(), 1);
1045        assert_eq!(info_message.pools.unwrap().len(), 1);
1046    }
1047
1048    #[test]
1049    fn test_serialization_deserialization() {
1050        let disk = Disk {
1051            endpoint: "http://localhost:9000".to_string(),
1052            state: "online".to_string(),
1053            total_space: 1000000000,
1054            used_space: 500000000,
1055            ..Default::default()
1056        };
1057
1058        let json = serde_json::to_string(&disk).unwrap();
1059        let deserialized: Disk = serde_json::from_str(&json).unwrap();
1060
1061        assert_eq!(deserialized.endpoint, "http://localhost:9000");
1062        assert_eq!(deserialized.state, "online");
1063        assert_eq!(deserialized.total_space, 1000000000);
1064        assert_eq!(deserialized.used_space, 500000000);
1065    }
1066
1067    #[test]
1068    fn test_debug_format_all_structures() {
1069        let item_state = ItemState::Online;
1070        let disk_metrics = DiskMetrics::default();
1071        let disk = Disk::default();
1072        let healing_disk = HealingDisk::default();
1073        let backend_byte = BackendByte::default();
1074        let storage_info = StorageInfo {
1075            disks: vec![],
1076            backend: BackendInfo::default(),
1077        };
1078        let backend_info = BackendInfo::default();
1079        let mem_stats = MemStats::default();
1080        let server_props = ServerProperties::default();
1081
1082        // Test that all structures can be formatted with Debug
1083        assert!(!format!("{item_state:?}").is_empty());
1084        assert!(!format!("{disk_metrics:?}").is_empty());
1085        assert!(!format!("{disk:?}").is_empty());
1086        assert!(!format!("{healing_disk:?}").is_empty());
1087        assert!(!format!("{backend_byte:?}").is_empty());
1088        assert!(!format!("{storage_info:?}").is_empty());
1089        assert!(!format!("{backend_info:?}").is_empty());
1090        assert!(!format!("{mem_stats:?}").is_empty());
1091        assert!(!format!("{server_props:?}").is_empty());
1092    }
1093
1094    #[test]
1095    fn test_memory_efficiency() {
1096        // Test that structures don't use excessive memory
1097        assert!(std::mem::size_of::<ItemState>() < 100);
1098        assert!(std::mem::size_of::<BackendByte>() < 100);
1099        assert!(std::mem::size_of::<BackendType>() < 100);
1100        assert!(std::mem::size_of::<MemStats>() < 1000);
1101        assert!(std::mem::size_of::<Buckets>() < 1000);
1102        assert!(std::mem::size_of::<Objects>() < 1000);
1103        assert!(std::mem::size_of::<Usage>() < 1000);
1104    }
1105
1106    #[test]
1107    fn test_constants() {
1108        assert_eq!(ITEM_OFFLINE, "offline");
1109        assert_eq!(ITEM_INITIALIZING, "initializing");
1110        assert_eq!(ITEM_ONLINE, "online");
1111    }
1112}