refactor: Restructure project layout and clean up dependencies (#30)

This commit introduces a significant reorganization of the project structure to improve maintainability and clarity.

Key changes include:
- Adjusted the directory layout for a more logical module organization.
- Removed unused crate dependencies, reducing the overall project size and potentially speeding up build times.
- Updated import paths and configuration files to reflect the structural changes.
This commit is contained in:
houseme
2025-07-02 19:33:12 +08:00
committed by GitHub
parent 0be4264eb1
commit 5826396cd0
322 changed files with 977 additions and 1542 deletions
+45
View File
@@ -0,0 +1,45 @@
// 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 serde::Deserialize;
use serde::Serialize;
use time::OffsetDateTime;
#[derive(Debug, Serialize, Deserialize, Default)]
#[serde(rename_all = "lowercase")]
pub enum GroupStatus {
#[default]
Enabled,
Disabled,
}
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct GroupAddRemove {
pub group: String,
pub members: Vec<String>,
#[serde(rename = "groupStatus")]
pub status: GroupStatus,
#[serde(rename = "isRemove")]
pub is_remove: bool,
}
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct GroupDesc {
pub name: String,
pub status: String,
pub members: Vec<String>,
pub policy: String,
#[serde(rename = "updatedAt", skip_serializing_if = "Option::is_none")]
pub updated_at: Option<OffsetDateTime>,
}
+60
View File
@@ -0,0 +1,60 @@
// 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 serde::{Deserialize, Serialize};
pub type HealItemType = String;
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct HealDriveInfo {
pub uuid: String,
pub endpoint: String,
pub state: String,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct Infos {
#[serde(rename = "drives")]
pub drives: Vec<HealDriveInfo>,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct HealResultItem {
#[serde(rename = "resultId")]
pub result_index: usize,
#[serde(rename = "type")]
pub heal_item_type: HealItemType,
#[serde(rename = "bucket")]
pub bucket: String,
#[serde(rename = "object")]
pub object: String,
#[serde(rename = "versionId")]
pub version_id: String,
#[serde(rename = "detail")]
pub detail: String,
#[serde(rename = "parityBlocks")]
pub parity_blocks: usize,
#[serde(rename = "dataBlocks")]
pub data_blocks: usize,
#[serde(rename = "diskCount")]
pub disk_count: usize,
#[serde(rename = "setCount")]
pub set_count: usize,
#[serde(rename = "before")]
pub before: Infos,
#[serde(rename = "after")]
pub after: Infos,
#[serde(rename = "objectSize")]
pub object_size: usize,
}
+700
View File
@@ -0,0 +1,700 @@
// 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;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct NodeCommon {
pub addr: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
}
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct Cpu {
pub vendor_id: String,
pub family: String,
pub model: String,
pub stepping: i32,
pub physical_id: String,
pub model_name: String,
pub mhz: f64,
pub cache_size: i32,
pub flags: Vec<String>,
pub microcode: String,
pub cores: u64,
}
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct CpuFreqStats {
name: String,
cpuinfo_current_frequency: Option<u64>,
cpuinfo_minimum_frequency: Option<u64>,
cpuinfo_maximum_frequency: Option<u64>,
cpuinfo_transition_latency: Option<u64>,
scaling_current_frequency: Option<u64>,
scaling_minimum_frequency: Option<u64>,
scaling_maximum_frequency: Option<u64>,
available_governors: String,
driver: String,
governor: String,
related_cpus: String,
set_speed: String,
}
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct Cpus {
node_common: NodeCommon,
cpus: Vec<Cpu>,
cpu_freq_stats: Vec<CpuFreqStats>,
}
pub fn get_cpus() -> Cpus {
// todo
Cpus::default()
}
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct Partition {
pub error: String,
device: String,
model: String,
revision: String,
mountpoint: String,
fs_type: String,
mount_options: String,
space_total: u64,
space_free: u64,
inode_total: u64,
inode_free: u64,
}
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct Partitions {
node_common: NodeCommon,
partitions: Vec<Partition>,
}
pub fn get_partitions() -> Partitions {
Partitions::default()
}
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct OsInfo {
node_common: NodeCommon,
}
pub fn get_os_info() -> OsInfo {
OsInfo::default()
}
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct ProcInfo {
node_common: NodeCommon,
pid: i32,
is_background: bool,
cpu_percent: f64,
children_pids: Vec<i32>,
cmd_line: String,
num_connections: usize,
create_time: u64,
cwd: String,
exec_path: String,
gids: Vec<i32>,
// io_counters:
is_running: bool,
// mem_info:
// mem_maps:
mem_percent: f32,
name: String,
nice: i32,
//num_ctx_switches:
num_fds: i32,
num_threads: i32,
// page_faults:
ppid: i32,
status: String,
tgid: i32,
uids: Vec<i32>,
username: String,
}
pub fn get_proc_info(_addr: &str) -> ProcInfo {
ProcInfo::default()
}
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct SysService {
name: String,
status: String,
}
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct SysServices {
node_common: NodeCommon,
services: Vec<SysService>,
}
pub fn get_sys_services(_add: &str) -> SysServices {
SysServices::default()
}
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct SysConfig {
node_common: NodeCommon,
config: HashMap<String, String>,
}
pub fn get_sys_config(_addr: &str) -> SysConfig {
SysConfig::default()
}
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct SysErrors {
node_common: NodeCommon,
errors: Vec<String>,
}
pub fn get_sys_errors(_add: &str) -> SysErrors {
SysErrors::default()
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct MemInfo {
node_common: NodeCommon,
#[serde(skip_serializing_if = "Option::is_none")]
total: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
used: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
free: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
available: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
shared: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
cache: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
buffers: Option<u64>,
#[serde(rename = "swap_space_total", skip_serializing_if = "Option::is_none")]
swap_space_total: Option<u64>,
#[serde(rename = "swap_space_free", skip_serializing_if = "Option::is_none")]
swap_space_free: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
limit: Option<u64>,
}
pub fn get_mem_info(_addr: &str) -> MemInfo {
MemInfo::default()
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json;
#[test]
fn test_node_common_creation() {
let node = NodeCommon::default();
assert!(node.addr.is_empty(), "Default addr should be empty");
assert!(node.error.is_none(), "Default error should be None");
}
#[test]
fn test_node_common_with_values() {
let node = NodeCommon {
addr: "127.0.0.1:9000".to_string(),
error: Some("Connection failed".to_string()),
};
assert_eq!(node.addr, "127.0.0.1:9000");
assert_eq!(node.error.unwrap(), "Connection failed");
}
#[test]
fn test_node_common_serialization() {
let node = NodeCommon {
addr: "localhost:8080".to_string(),
error: None,
};
let json = serde_json::to_string(&node).unwrap();
assert!(json.contains("localhost:8080"));
assert!(!json.contains("error"), "None error should be skipped in serialization");
}
#[test]
fn test_node_common_deserialization() {
let json = r#"{"addr":"test.example.com:9000","error":"Test error"}"#;
let node: NodeCommon = serde_json::from_str(json).unwrap();
assert_eq!(node.addr, "test.example.com:9000");
assert_eq!(node.error.unwrap(), "Test error");
}
#[test]
fn test_cpu_default() {
let cpu = Cpu::default();
assert!(cpu.vendor_id.is_empty());
assert!(cpu.family.is_empty());
assert!(cpu.model.is_empty());
assert_eq!(cpu.stepping, 0);
assert_eq!(cpu.mhz, 0.0);
assert_eq!(cpu.cache_size, 0);
assert!(cpu.flags.is_empty());
assert_eq!(cpu.cores, 0);
}
#[test]
fn test_cpu_with_values() {
let cpu = Cpu {
vendor_id: "GenuineIntel".to_string(),
family: "6".to_string(),
model: "142".to_string(),
stepping: 12,
physical_id: "0".to_string(),
model_name: "Intel(R) Core(TM) i7-8565U CPU @ 1.80GHz".to_string(),
mhz: 1800.0,
cache_size: 8192,
flags: vec!["fpu".to_string(), "vme".to_string(), "de".to_string()],
microcode: "0xf0".to_string(),
cores: 4,
};
assert_eq!(cpu.vendor_id, "GenuineIntel");
assert_eq!(cpu.cores, 4);
assert_eq!(cpu.flags.len(), 3);
assert!(cpu.flags.contains(&"fpu".to_string()));
}
#[test]
fn test_cpu_serialization() {
let cpu = Cpu {
vendor_id: "AMD".to_string(),
model_name: "AMD Ryzen 7".to_string(),
cores: 8,
..Default::default()
};
let json = serde_json::to_string(&cpu).unwrap();
assert!(json.contains("AMD"));
assert!(json.contains("AMD Ryzen 7"));
assert!(json.contains("8"));
}
#[test]
fn test_cpu_freq_stats_default() {
let stats = CpuFreqStats::default();
assert!(stats.name.is_empty());
assert!(stats.cpuinfo_current_frequency.is_none());
assert!(stats.available_governors.is_empty());
assert!(stats.driver.is_empty());
}
#[test]
fn test_cpus_structure() {
let cpus = Cpus {
node_common: NodeCommon {
addr: "node1".to_string(),
error: None,
},
cpus: vec![Cpu {
vendor_id: "Intel".to_string(),
cores: 4,
..Default::default()
}],
cpu_freq_stats: vec![CpuFreqStats {
name: "cpu0".to_string(),
cpuinfo_current_frequency: Some(2400),
..Default::default()
}],
};
assert_eq!(cpus.node_common.addr, "node1");
assert_eq!(cpus.cpus.len(), 1);
assert_eq!(cpus.cpu_freq_stats.len(), 1);
assert_eq!(cpus.cpus[0].cores, 4);
}
#[test]
fn test_get_cpus_function() {
let cpus = get_cpus();
assert!(cpus.node_common.addr.is_empty());
assert!(cpus.cpus.is_empty());
assert!(cpus.cpu_freq_stats.is_empty());
}
#[test]
fn test_partition_default() {
let partition = Partition::default();
assert!(partition.error.is_empty());
assert!(partition.device.is_empty());
assert_eq!(partition.space_total, 0);
assert_eq!(partition.space_free, 0);
assert_eq!(partition.inode_total, 0);
assert_eq!(partition.inode_free, 0);
}
#[test]
fn test_partition_with_values() {
let partition = Partition {
error: "".to_string(),
device: "/dev/sda1".to_string(),
model: "Samsung SSD".to_string(),
revision: "1.0".to_string(),
mountpoint: "/".to_string(),
fs_type: "ext4".to_string(),
mount_options: "rw,relatime".to_string(),
space_total: 1000000000,
space_free: 500000000,
inode_total: 1000000,
inode_free: 800000,
};
assert_eq!(partition.device, "/dev/sda1");
assert_eq!(partition.fs_type, "ext4");
assert_eq!(partition.space_total, 1000000000);
assert_eq!(partition.space_free, 500000000);
}
#[test]
fn test_partitions_structure() {
let partitions = Partitions {
node_common: NodeCommon {
addr: "storage-node".to_string(),
error: None,
},
partitions: vec![
Partition {
device: "/dev/sda1".to_string(),
mountpoint: "/".to_string(),
space_total: 1000000,
space_free: 500000,
..Default::default()
},
Partition {
device: "/dev/sdb1".to_string(),
mountpoint: "/data".to_string(),
space_total: 2000000,
space_free: 1500000,
..Default::default()
},
],
};
assert_eq!(partitions.partitions.len(), 2);
assert_eq!(partitions.partitions[0].device, "/dev/sda1");
assert_eq!(partitions.partitions[1].mountpoint, "/data");
}
#[test]
fn test_get_partitions_function() {
let partitions = get_partitions();
assert!(partitions.node_common.addr.is_empty());
assert!(partitions.partitions.is_empty());
}
#[test]
fn test_os_info_default() {
let os_info = OsInfo::default();
assert!(os_info.node_common.addr.is_empty());
assert!(os_info.node_common.error.is_none());
}
#[test]
fn test_get_os_info_function() {
let os_info = get_os_info();
assert!(os_info.node_common.addr.is_empty());
}
#[test]
fn test_proc_info_default() {
let proc_info = ProcInfo::default();
assert_eq!(proc_info.pid, 0);
assert!(!proc_info.is_background);
assert_eq!(proc_info.cpu_percent, 0.0);
assert!(proc_info.children_pids.is_empty());
assert!(proc_info.cmd_line.is_empty());
assert_eq!(proc_info.num_connections, 0);
assert!(!proc_info.is_running);
assert_eq!(proc_info.mem_percent, 0.0);
assert!(proc_info.name.is_empty());
assert_eq!(proc_info.nice, 0);
assert_eq!(proc_info.num_fds, 0);
assert_eq!(proc_info.num_threads, 0);
assert_eq!(proc_info.ppid, 0);
assert!(proc_info.status.is_empty());
assert_eq!(proc_info.tgid, 0);
assert!(proc_info.uids.is_empty());
assert!(proc_info.username.is_empty());
}
#[test]
fn test_proc_info_with_values() {
let proc_info = ProcInfo {
node_common: NodeCommon {
addr: "worker-node".to_string(),
error: None,
},
pid: 1234,
is_background: true,
cpu_percent: 15.5,
children_pids: vec![1235, 1236],
cmd_line: "rustfs --config /etc/rustfs.conf".to_string(),
num_connections: 10,
create_time: 1640995200,
cwd: "/opt/rustfs".to_string(),
exec_path: "/usr/bin/rustfs".to_string(),
gids: vec![1000, 1001],
is_running: true,
mem_percent: 8.2,
name: "rustfs".to_string(),
nice: 0,
num_fds: 25,
num_threads: 4,
ppid: 1,
status: "running".to_string(),
tgid: 1234,
uids: vec![1000],
username: "rustfs".to_string(),
};
assert_eq!(proc_info.pid, 1234);
assert!(proc_info.is_background);
assert_eq!(proc_info.cpu_percent, 15.5);
assert_eq!(proc_info.children_pids.len(), 2);
assert_eq!(proc_info.name, "rustfs");
assert!(proc_info.is_running);
}
#[test]
fn test_get_proc_info_function() {
let proc_info = get_proc_info("127.0.0.1:9000");
assert_eq!(proc_info.pid, 0);
assert!(!proc_info.is_running);
}
#[test]
fn test_sys_service_default() {
let service = SysService::default();
assert!(service.name.is_empty());
assert!(service.status.is_empty());
}
#[test]
fn test_sys_service_with_values() {
let service = SysService {
name: "rustfs".to_string(),
status: "active".to_string(),
};
assert_eq!(service.name, "rustfs");
assert_eq!(service.status, "active");
}
#[test]
fn test_sys_services_structure() {
let services = SysServices {
node_common: NodeCommon {
addr: "service-node".to_string(),
error: None,
},
services: vec![
SysService {
name: "rustfs".to_string(),
status: "active".to_string(),
},
SysService {
name: "nginx".to_string(),
status: "inactive".to_string(),
},
],
};
assert_eq!(services.services.len(), 2);
assert_eq!(services.services[0].name, "rustfs");
assert_eq!(services.services[1].status, "inactive");
}
#[test]
fn test_get_sys_services_function() {
let services = get_sys_services("localhost");
assert!(services.node_common.addr.is_empty());
assert!(services.services.is_empty());
}
#[test]
fn test_sys_config_default() {
let config = SysConfig::default();
assert!(config.node_common.addr.is_empty());
assert!(config.config.is_empty());
}
#[test]
fn test_sys_config_with_values() {
let mut config_map = HashMap::new();
config_map.insert("max_connections".to_string(), "1000".to_string());
config_map.insert("timeout".to_string(), "30".to_string());
let config = SysConfig {
node_common: NodeCommon {
addr: "config-node".to_string(),
error: None,
},
config: config_map,
};
assert_eq!(config.config.len(), 2);
assert_eq!(config.config.get("max_connections").unwrap(), "1000");
assert_eq!(config.config.get("timeout").unwrap(), "30");
}
#[test]
fn test_get_sys_config_function() {
let config = get_sys_config("192.168.1.100");
assert!(config.node_common.addr.is_empty());
assert!(config.config.is_empty());
}
#[test]
fn test_sys_errors_default() {
let errors = SysErrors::default();
assert!(errors.node_common.addr.is_empty());
assert!(errors.errors.is_empty());
}
#[test]
fn test_sys_errors_with_values() {
let errors = SysErrors {
node_common: NodeCommon {
addr: "error-node".to_string(),
error: None,
},
errors: vec![
"Connection timeout".to_string(),
"Memory allocation failed".to_string(),
"Disk full".to_string(),
],
};
assert_eq!(errors.errors.len(), 3);
assert!(errors.errors.contains(&"Connection timeout".to_string()));
assert!(errors.errors.contains(&"Disk full".to_string()));
}
#[test]
fn test_get_sys_errors_function() {
let errors = get_sys_errors("test-node");
assert!(errors.node_common.addr.is_empty());
assert!(errors.errors.is_empty());
}
#[test]
fn test_mem_info_default() {
let mem_info = MemInfo::default();
assert!(mem_info.node_common.addr.is_empty());
assert!(mem_info.total.is_none());
assert!(mem_info.used.is_none());
assert!(mem_info.free.is_none());
assert!(mem_info.available.is_none());
assert!(mem_info.shared.is_none());
assert!(mem_info.cache.is_none());
assert!(mem_info.buffers.is_none());
assert!(mem_info.swap_space_total.is_none());
assert!(mem_info.swap_space_free.is_none());
assert!(mem_info.limit.is_none());
}
#[test]
fn test_mem_info_with_values() {
let mem_info = MemInfo {
node_common: NodeCommon {
addr: "memory-node".to_string(),
error: None,
},
total: Some(16777216000),
used: Some(8388608000),
free: Some(4194304000),
available: Some(12582912000),
shared: Some(1048576000),
cache: Some(2097152000),
buffers: Some(524288000),
swap_space_total: Some(4294967296),
swap_space_free: Some(2147483648),
limit: Some(16777216000),
};
assert_eq!(mem_info.total.unwrap(), 16777216000);
assert_eq!(mem_info.used.unwrap(), 8388608000);
assert_eq!(mem_info.free.unwrap(), 4194304000);
assert_eq!(mem_info.swap_space_total.unwrap(), 4294967296);
}
#[test]
fn test_mem_info_serialization() {
let mem_info = MemInfo {
node_common: NodeCommon {
addr: "test-node".to_string(),
error: None,
},
total: Some(8000000000),
used: Some(4000000000),
free: None,
available: Some(6000000000),
..Default::default()
};
let json = serde_json::to_string(&mem_info).unwrap();
assert!(json.contains("8000000000"));
assert!(json.contains("4000000000"));
assert!(json.contains("6000000000"));
assert!(!json.contains("free"), "None values should be skipped");
}
#[test]
fn test_get_mem_info_function() {
let mem_info = get_mem_info("memory-server");
assert!(mem_info.node_common.addr.is_empty());
assert!(mem_info.total.is_none());
assert!(mem_info.used.is_none());
}
#[test]
fn test_all_structures_debug_format() {
let node = NodeCommon::default();
let cpu = Cpu::default();
let partition = Partition::default();
let proc_info = ProcInfo::default();
let service = SysService::default();
let mem_info = MemInfo::default();
// Test that all structures can be formatted with Debug
assert!(!format!("{node:?}").is_empty());
assert!(!format!("{cpu:?}").is_empty());
assert!(!format!("{partition:?}").is_empty());
assert!(!format!("{proc_info:?}").is_empty());
assert!(!format!("{service:?}").is_empty());
assert!(!format!("{mem_info:?}").is_empty());
}
#[test]
fn test_memory_efficiency() {
// Test that structures don't use excessive memory
assert!(std::mem::size_of::<NodeCommon>() < 1000);
assert!(std::mem::size_of::<Cpu>() < 2000);
assert!(std::mem::size_of::<Partition>() < 2000);
assert!(std::mem::size_of::<MemInfo>() < 1000);
}
}
File diff suppressed because it is too large Load Diff
+30
View File
@@ -0,0 +1,30 @@
// 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.
pub mod group;
pub mod heal_commands;
pub mod health;
pub mod info_commands;
pub mod metrics;
pub mod net;
pub mod policy;
pub mod service_commands;
pub mod trace;
pub mod user;
pub mod utils;
pub use group::*;
pub use info_commands::*;
pub use policy::*;
pub use user::*;
+670
View File
@@ -0,0 +1,670 @@
// 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;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use crate::health::MemInfo;
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct TimedAction {
#[serde(rename = "count")]
pub count: u64,
#[serde(rename = "acc_time_ns")]
pub acc_time: u64,
#[serde(rename = "bytes")]
pub bytes: u64,
}
impl TimedAction {
pub fn merge(&mut self, other: &TimedAction) {
self.count += other.count;
self.acc_time += other.acc_time;
self.bytes += other.bytes;
}
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct DiskIOStats {
#[serde(rename = "read_ios")]
pub read_ios: u64,
#[serde(rename = "read_merges")]
pub read_merges: u64,
#[serde(rename = "read_sectors")]
pub read_sectors: u64,
#[serde(rename = "read_ticks")]
pub read_ticks: u64,
#[serde(rename = "write_ios")]
pub write_ios: u64,
#[serde(rename = "write_merges")]
pub write_merges: u64,
#[serde(rename = "write_sectors")]
pub write_sectors: u64,
#[serde(rename = "write_ticks")]
pub write_ticks: u64,
#[serde(rename = "current_ios")]
pub current_ios: u64,
#[serde(rename = "total_ticks")]
pub total_ticks: u64,
#[serde(rename = "req_ticks")]
pub req_ticks: u64,
#[serde(rename = "discard_ios")]
pub discard_ios: u64,
#[serde(rename = "discard_merges")]
pub discard_merges: u64,
#[serde(rename = "discard_secotrs")]
pub discard_sectors: u64,
#[serde(rename = "discard_ticks")]
pub discard_ticks: u64,
#[serde(rename = "flush_ios")]
pub flush_ios: u64,
#[serde(rename = "flush_ticks")]
pub flush_ticks: u64,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct DiskMetric {
#[serde(rename = "collected")]
pub collected_at: DateTime<Utc>,
#[serde(rename = "n_disks")]
pub n_disks: usize,
#[serde(rename = "offline")]
pub offline: usize,
#[serde(rename = "healing")]
pub healing: usize,
#[serde(rename = "life_time_ops")]
pub life_time_ops: HashMap<String, u64>,
#[serde(rename = "last_minute")]
pub last_minute: Operations,
#[serde(rename = "iostats")]
pub io_stats: DiskIOStats,
}
impl DiskMetric {
pub fn merge(&mut self, other: &DiskMetric) {
if self.collected_at < other.collected_at {
self.collected_at = other.collected_at;
}
self.n_disks += other.n_disks;
self.offline += other.offline;
self.healing += other.healing;
for (k, v) in other.life_time_ops.iter() {
*self.life_time_ops.entry(k.clone()).or_insert(0) += v;
}
for (k, v) in other.last_minute.operations.iter() {
self.last_minute.operations.entry(k.clone()).or_default().merge(v);
}
}
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct LastMinute {
#[serde(rename = "actions")]
pub actions: HashMap<String, TimedAction>,
#[serde(rename = "ilm")]
pub ilm: HashMap<String, TimedAction>,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct ScannerMetrics {
#[serde(rename = "collected")]
pub collected_at: DateTime<Utc>,
#[serde(rename = "current_cycle")]
pub current_cycle: u64,
#[serde(rename = "current_started")]
pub current_started: DateTime<Utc>,
#[serde(rename = "cycle_complete_times")]
pub cycles_completed_at: Vec<DateTime<Utc>>,
#[serde(rename = "ongoing_buckets")]
pub ongoing_buckets: usize,
#[serde(rename = "life_time_ops")]
pub life_time_ops: HashMap<String, u64>,
#[serde(rename = "ilm_ops")]
pub life_time_ilm: HashMap<String, u64>,
#[serde(rename = "last_minute")]
pub last_minute: LastMinute,
#[serde(rename = "active")]
pub active_paths: Vec<String>,
}
impl ScannerMetrics {
pub fn merge(&mut self, other: &Self) {
if self.collected_at < other.collected_at {
self.collected_at = other.collected_at;
}
if self.ongoing_buckets < other.ongoing_buckets {
self.ongoing_buckets = other.ongoing_buckets;
}
if self.current_cycle < other.current_cycle {
self.current_cycle = other.current_cycle;
self.cycles_completed_at = other.cycles_completed_at.clone();
self.current_started = other.current_started;
}
if other.cycles_completed_at.len() > self.cycles_completed_at.len() {
self.cycles_completed_at = other.cycles_completed_at.clone();
}
if !other.life_time_ops.is_empty() && self.life_time_ops.is_empty() {
self.life_time_ops = other.life_time_ops.clone();
}
for (k, v) in other.life_time_ops.iter() {
*self.life_time_ops.entry(k.clone()).or_default() += v;
}
for (k, v) in other.last_minute.actions.iter() {
self.last_minute.actions.entry(k.clone()).or_default().merge(v);
}
for (k, v) in other.life_time_ilm.iter() {
*self.life_time_ilm.entry(k.clone()).or_default() += v;
}
for (k, v) in other.last_minute.ilm.iter() {
self.last_minute.ilm.entry(k.clone()).or_default().merge(v);
}
self.active_paths.extend(other.active_paths.clone());
self.active_paths.sort();
}
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct Metrics {
#[serde(rename = "scanner", skip_serializing_if = "Option::is_none")]
pub scanner: Option<ScannerMetrics>,
#[serde(rename = "disk", skip_serializing_if = "Option::is_none")]
pub disk: Option<DiskMetric>,
#[serde(rename = "os", skip_serializing_if = "Option::is_none")]
pub os: Option<OsMetrics>,
#[serde(rename = "batchJobs", skip_serializing_if = "Option::is_none")]
pub batch_jobs: Option<BatchJobMetrics>,
#[serde(rename = "siteResync", skip_serializing_if = "Option::is_none")]
pub site_resync: Option<SiteResyncMetrics>,
#[serde(rename = "net", skip_serializing_if = "Option::is_none")]
pub net: Option<NetMetrics>,
#[serde(rename = "mem", skip_serializing_if = "Option::is_none")]
pub mem: Option<MemMetrics>,
#[serde(rename = "cpu", skip_serializing_if = "Option::is_none")]
pub cpu: Option<CPUMetrics>,
#[serde(rename = "rpc", skip_serializing_if = "Option::is_none")]
pub rpc: Option<RPCMetrics>,
}
impl Metrics {
pub fn merge(&mut self, other: &Self) {
if let Some(scanner) = other.scanner.as_ref() {
match self.scanner {
Some(ref mut s_scanner) => s_scanner.merge(scanner),
None => self.scanner = Some(scanner.clone()),
}
}
if let Some(disk) = other.disk.as_ref() {
match self.disk {
Some(ref mut s_disk) => s_disk.merge(disk),
None => self.disk = Some(disk.clone()),
}
}
if let Some(os) = other.os.as_ref() {
match self.os {
Some(ref mut s_os) => s_os.merge(os),
None => self.os = Some(os.clone()),
}
}
if let Some(batch_jobs) = other.batch_jobs.as_ref() {
match self.batch_jobs {
Some(ref mut s_batch_jobs) => s_batch_jobs.merge(batch_jobs),
None => self.batch_jobs = Some(batch_jobs.clone()),
}
}
if let Some(site_resync) = other.site_resync.as_ref() {
match self.site_resync {
Some(ref mut s_site_resync) => s_site_resync.merge(site_resync),
None => self.site_resync = Some(site_resync.clone()),
}
}
if let Some(net) = other.net.as_ref() {
match self.net {
Some(ref mut s_net) => s_net.merge(net),
None => self.net = Some(net.clone()),
}
}
if let Some(rpc) = other.rpc.as_ref() {
match self.rpc {
Some(ref mut s_rpc) => s_rpc.merge(rpc),
None => self.rpc = Some(rpc.clone()),
}
}
}
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct RPCMetrics {
#[serde(rename = "collectedAt")]
pub collected_at: DateTime<Utc>,
pub connected: i32,
#[serde(rename = "reconnectCount")]
pub reconnect_count: i32,
pub disconnected: i32,
#[serde(rename = "outgoingStreams")]
pub outgoing_streams: i32,
#[serde(rename = "incomingStreams")]
pub incoming_streams: i32,
#[serde(rename = "outgoingBytes")]
pub outgoing_bytes: i64,
#[serde(rename = "incomingBytes")]
pub incoming_bytes: i64,
#[serde(rename = "outgoingMessages")]
pub outgoing_messages: i64,
#[serde(rename = "incomingMessages")]
pub incoming_messages: i64,
pub out_queue: i32,
#[serde(rename = "lastPongTime")]
pub last_pong_time: DateTime<Utc>,
#[serde(rename = "lastPingMS")]
pub last_ping_ms: f64,
#[serde(rename = "maxPingDurMS")]
pub max_ping_dur_ms: f64, // Maximum across all merged entries.
#[serde(rename = "lastConnectTime")]
pub last_connect_time: DateTime<Utc>,
#[serde(rename = "byDestination", skip_serializing_if = "Option::is_none")]
pub by_destination: Option<HashMap<String, RPCMetrics>>,
#[serde(rename = "byCaller", skip_serializing_if = "Option::is_none")]
pub by_caller: Option<HashMap<String, RPCMetrics>>,
}
impl RPCMetrics {
pub fn merge(&mut self, other: &Self) {
if self.collected_at < other.collected_at {
self.collected_at = other.collected_at;
}
if self.last_connect_time < other.last_connect_time {
self.last_connect_time = other.last_connect_time;
}
self.connected += other.connected;
self.disconnected += other.disconnected;
self.reconnect_count += other.reconnect_count;
self.outgoing_streams += other.outgoing_streams;
self.incoming_streams += other.incoming_streams;
self.outgoing_bytes += other.outgoing_bytes;
self.incoming_bytes += other.incoming_bytes;
self.outgoing_messages += other.outgoing_messages;
self.incoming_messages += other.incoming_messages;
self.out_queue += other.out_queue;
if self.last_pong_time < other.last_pong_time {
self.last_pong_time = other.last_pong_time;
self.last_ping_ms = other.last_ping_ms;
}
if self.max_ping_dur_ms < other.max_ping_dur_ms {
self.max_ping_dur_ms = other.max_ping_dur_ms;
}
if let Some(by_destination) = other.by_destination.as_ref() {
match self.by_destination.as_mut() {
Some(s_by_de) => {
for (key, value) in by_destination {
s_by_de
.entry(key.to_string())
.and_modify(|v| v.merge(value))
.or_insert(value.clone());
}
}
None => self.by_destination = Some(by_destination.clone()),
}
}
if let Some(by_caller) = other.by_caller.as_ref() {
match self.by_caller.as_mut() {
Some(s_by_caller) => {
for (key, value) in by_caller {
s_by_caller
.entry(key.to_string())
.and_modify(|v| v.merge(value))
.or_insert(value.clone());
}
}
None => self.by_caller = Some(by_caller.clone()),
}
}
}
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct CPUMetrics {}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct NetMetrics {
#[serde(rename = "collected")]
pub collected_at: DateTime<Utc>,
#[serde(rename = "interfaceName")]
pub interface_name: String,
#[serde(rename = "netstats")]
pub net_stats: NetDevLine,
}
impl NetMetrics {
pub fn merge(&mut self, other: &Self) {
if self.collected_at < other.collected_at {
self.collected_at = other.collected_at;
}
self.net_stats.rx_bytes += other.net_stats.rx_bytes;
self.net_stats.rx_packets += other.net_stats.rx_packets;
self.net_stats.rx_errors += other.net_stats.rx_errors;
self.net_stats.rx_dropped += other.net_stats.rx_dropped;
self.net_stats.rx_fifo += other.net_stats.rx_fifo;
self.net_stats.rx_frame += other.net_stats.rx_frame;
self.net_stats.rx_compressed += other.net_stats.rx_compressed;
self.net_stats.rx_multicast += other.net_stats.rx_multicast;
self.net_stats.tx_bytes += other.net_stats.tx_bytes;
self.net_stats.tx_packets += other.net_stats.tx_packets;
self.net_stats.tx_errors += other.net_stats.tx_errors;
self.net_stats.tx_dropped += other.net_stats.tx_dropped;
self.net_stats.tx_fifo += other.net_stats.tx_fifo;
self.net_stats.tx_collisions += other.net_stats.tx_collisions;
self.net_stats.tx_carrier += other.net_stats.tx_carrier;
self.net_stats.tx_compressed += other.net_stats.tx_compressed;
}
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct NetDevLine {
#[serde(rename = "name")]
pub name: String, // The name of the interface.
#[serde(rename = "rx_bytes")]
pub rx_bytes: u64, // Cumulative count of bytes received.
#[serde(rename = "rx_packets")]
pub rx_packets: u64, // Cumulative count of packets received.
#[serde(rename = "rx_errors")]
pub rx_errors: u64, // Cumulative count of receive errors encountered.
#[serde(rename = "rx_dropped")]
pub rx_dropped: u64, // Cumulative count of packets dropped while receiving.
#[serde(rename = "rx_fifo")]
pub rx_fifo: u64, // Cumulative count of FIFO buffer errors.
#[serde(rename = "rx_frame")]
pub rx_frame: u64, // Cumulative count of packet framing errors.
#[serde(rename = "rx_compressed")]
pub rx_compressed: u64, // Cumulative count of compressed packets received by the device driver.
#[serde(rename = "rx_multicast")]
pub rx_multicast: u64, // Cumulative count of multicast frames received by the device driver.
#[serde(rename = "tx_bytes")]
pub tx_bytes: u64, // Cumulative count of bytes transmitted.
#[serde(rename = "tx_packets")]
pub tx_packets: u64, // Cumulative count of packets transmitted.
#[serde(rename = "tx_errors")]
pub tx_errors: u64, // Cumulative count of transmit errors encountered.
#[serde(rename = "tx_dropped")]
pub tx_dropped: u64, // Cumulative count of packets dropped while transmitting.
#[serde(rename = "tx_fifo")]
pub tx_fifo: u64, // Cumulative count of FIFO buffer errors.
#[serde(rename = "tx_collisions")]
pub tx_collisions: u64, // Cumulative count of collisions detected on the interface.
#[serde(rename = "tx_carrier")]
pub tx_carrier: u64, // Cumulative count of carrier losses detected by the device driver.
#[serde(rename = "tx_compressed")]
pub tx_compressed: u64, // Cumulative count of compressed packets transmitted by the device driver.
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct MemMetrics {
#[serde(rename = "collected")]
pub collected_at: DateTime<Utc>,
#[serde(rename = "memInfo")]
pub info: MemInfo,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct SiteResyncMetrics {
#[serde(rename = "collected")]
pub collected_at: DateTime<Utc>,
#[serde(rename = "resyncStatus", skip_serializing_if = "Option::is_none")]
pub resync_status: Option<String>,
#[serde(rename = "startTime")]
pub start_time: DateTime<Utc>,
#[serde(rename = "lastUpdate")]
pub last_update: DateTime<Utc>,
#[serde(rename = "numBuckets")]
pub num_buckets: i64,
#[serde(rename = "resyncID")]
pub resync_id: String,
#[serde(rename = "deplID")]
pub depl_id: String,
#[serde(rename = "completedReplicationSize")]
pub replicated_size: i64,
#[serde(rename = "replicationCount")]
pub replicated_count: i64,
#[serde(rename = "failedReplicationSize")]
pub failed_size: i64,
#[serde(rename = "failedReplicationCount")]
pub failed_count: i64,
#[serde(rename = "failedBuckets")]
pub failed_buckets: Vec<String>,
#[serde(rename = "bucket", skip_serializing_if = "Option::is_none")]
pub bucket: Option<String>,
#[serde(rename = "object", skip_serializing_if = "Option::is_none")]
pub object: Option<String>,
}
impl SiteResyncMetrics {
pub fn merge(&mut self, other: &Self) {
if self.collected_at < other.collected_at {
*self = other.clone();
}
}
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct BatchJobMetrics {
#[serde(rename = "collected")]
pub collected_at: DateTime<Utc>,
#[serde(rename = "Jobs")]
pub jobs: HashMap<String, JobMetric>,
}
impl BatchJobMetrics {
pub fn merge(&mut self, other: &BatchJobMetrics) {
if other.jobs.is_empty() {
return;
}
if self.collected_at < other.collected_at {
self.collected_at = other.collected_at;
}
for (k, v) in other.jobs.clone().into_iter() {
self.jobs.insert(k, v);
}
}
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct JobMetric {
#[serde(rename = "jobID")]
pub job_id: String,
#[serde(rename = "jobType")]
pub job_type: String,
#[serde(rename = "startTime")]
pub start_time: DateTime<Utc>,
#[serde(rename = "lastUpdate")]
pub last_update: DateTime<Utc>,
#[serde(rename = "retryAttempts")]
pub retry_attempts: i32,
pub complete: bool,
pub failed: bool,
// Specific job type data
#[serde(skip_serializing_if = "Option::is_none")]
pub replicate: Option<ReplicateInfo>,
#[serde(skip_serializing_if = "Option::is_none")]
pub key_rotate: Option<KeyRotationInfo>,
#[serde(skip_serializing_if = "Option::is_none")]
pub expired: Option<ExpirationInfo>,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct ReplicateInfo {
#[serde(rename = "lastBucket")]
pub bucket: String,
#[serde(rename = "lastObject")]
pub object: String,
#[serde(rename = "objects")]
pub objects: i64,
#[serde(rename = "objectsFailed")]
pub objects_failed: i64,
#[serde(rename = "bytesTransferred")]
pub bytes_transferred: i64,
#[serde(rename = "bytesFailed")]
pub bytes_failed: i64,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct ExpirationInfo {
#[serde(rename = "lastBucket")]
pub bucket: String,
#[serde(rename = "lastObject")]
pub object: String,
#[serde(rename = "objects")]
pub objects: i64,
#[serde(rename = "objectsFailed")]
pub objects_failed: i64,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct KeyRotationInfo {
#[serde(rename = "lastBucket")]
pub bucket: String,
#[serde(rename = "lastObject")]
pub object: String,
#[serde(rename = "objects")]
pub objects: i64,
#[serde(rename = "objectsFailed")]
pub objects_failed: i64,
}
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct RealtimeMetrics {
#[serde(rename = "errors")]
pub errors: Vec<String>,
#[serde(rename = "hosts")]
pub hosts: Vec<String>,
#[serde(rename = "aggregated")]
pub aggregated: Metrics,
#[serde(rename = "by_host")]
pub by_host: HashMap<String, Metrics>,
#[serde(rename = "by_disk")]
pub by_disk: HashMap<String, DiskMetric>,
#[serde(rename = "final")]
pub finally: bool,
}
impl RealtimeMetrics {
pub fn merge(&mut self, other: Self) {
if !other.errors.is_empty() {
self.errors.extend(other.errors);
}
for (k, v) in other.by_host.into_iter() {
*self.by_host.entry(k).or_default() = v;
}
self.hosts.extend(other.hosts);
self.aggregated.merge(&other.aggregated);
self.hosts.sort();
for (k, v) in other.by_disk.into_iter() {
self.by_disk.entry(k.to_string()).and_modify(|h| *h = v.clone()).or_insert(v);
}
}
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct OsMetrics {
#[serde(rename = "collected")]
pub collected_at: DateTime<Utc>,
#[serde(rename = "life_time_ops")]
pub life_time_ops: HashMap<String, u64>,
#[serde(rename = "last_minute")]
pub last_minute: Operations,
}
impl OsMetrics {
pub fn merge(&mut self, other: &Self) {
if self.collected_at < other.collected_at {
self.collected_at = other.collected_at;
}
for (k, v) in other.life_time_ops.iter() {
*self.life_time_ops.entry(k.clone()).or_default() += v;
}
for (k, v) in other.last_minute.operations.iter() {
self.last_minute.operations.entry(k.clone()).or_default().merge(v);
}
}
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct Operations {
#[serde(rename = "operations")]
pub operations: HashMap<String, TimedAction>,
}
+46
View File
@@ -0,0 +1,46 @@
// 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 serde::{Deserialize, Serialize};
use crate::health::NodeCommon;
#[cfg(target_os = "linux")]
pub fn get_net_info(addr: &str, iface: &str) -> NetInfo {
let mut ni = NetInfo::default();
ni.node_common.addr = addr.to_string();
ni.interface = iface.to_string();
ni
}
#[cfg(not(target_os = "linux"))]
pub fn get_net_info(addr: &str, iface: &str) -> NetInfo {
NetInfo {
node_common: NodeCommon {
addr: addr.to_owned(),
error: Some("Not implemented for non-linux platforms".to_string()),
},
interface: iface.to_owned(),
..Default::default()
}
}
#[derive(Debug, Default, Serialize, Deserialize)]
pub struct NetInfo {
node_common: NodeCommon,
interface: String,
driver: String,
firmware_version: String,
}
+28
View File
@@ -0,0 +1,28 @@
// 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 serde::Deserialize;
use serde::Serialize;
use serde_json::Value;
use time::OffsetDateTime;
#[derive(Debug, Serialize, Deserialize)]
pub struct PolicyInfo {
pub policy_name: String,
pub policy: Value,
#[serde(skip_serializing_if = "Option::is_none")]
pub create_date: Option<OffsetDateTime>,
#[serde(skip_serializing_if = "Option::is_none")]
pub update_date: Option<OffsetDateTime>,
}
+119
View File
@@ -0,0 +1,119 @@
// 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::Duration};
use hyper::Uri;
use crate::{trace::TraceType, utils::parse_duration};
#[derive(Debug, Default)]
#[allow(dead_code)]
pub struct ServiceTraceOpts {
s3: bool,
internal: bool,
storage: bool,
os: bool,
scanner: bool,
decommission: bool,
healing: bool,
batch_replication: bool,
batch_key_rotation: bool,
batch_expire: bool,
batch_all: bool,
rebalance: bool,
replication_resync: bool,
bootstrap: bool,
ftp: bool,
ilm: bool,
only_errors: bool,
threshold: Duration,
}
#[allow(dead_code)]
impl ServiceTraceOpts {
fn trace_types(&self) -> TraceType {
let mut tt = TraceType::default();
tt.set_if(self.s3, &TraceType::S3);
tt.set_if(self.internal, &TraceType::INTERNAL);
tt.set_if(self.storage, &TraceType::STORAGE);
tt.set_if(self.os, &TraceType::OS);
tt.set_if(self.scanner, &TraceType::SCANNER);
tt.set_if(self.decommission, &TraceType::DECOMMISSION);
tt.set_if(self.healing, &TraceType::HEALING);
if self.batch_all {
tt.set_if(true, &TraceType::BATCH_REPLICATION);
tt.set_if(true, &TraceType::BATCH_KEY_ROTATION);
tt.set_if(true, &TraceType::BATCH_EXPIRE);
} else {
tt.set_if(self.batch_replication, &TraceType::BATCH_REPLICATION);
tt.set_if(self.batch_key_rotation, &TraceType::BATCH_KEY_ROTATION);
tt.set_if(self.batch_expire, &TraceType::BATCH_EXPIRE);
}
tt.set_if(self.rebalance, &TraceType::REBALANCE);
tt.set_if(self.replication_resync, &TraceType::REPLICATION_RESYNC);
tt.set_if(self.bootstrap, &TraceType::BOOTSTRAP);
tt.set_if(self.ftp, &TraceType::FTP);
tt.set_if(self.ilm, &TraceType::ILM);
tt
}
pub fn parse_params(&mut self, uri: &Uri) -> Result<(), String> {
let query_pairs: HashMap<_, _> = uri
.query()
.unwrap_or("")
.split('&')
.filter_map(|pair| {
let mut split = pair.split('=');
let key = split.next()?.to_string();
let value = split.next().map(|v| v.to_string()).unwrap_or_else(|| "false".to_string());
Some((key, value))
})
.collect();
self.s3 = query_pairs.get("s3").is_some_and(|v| v == "true");
self.os = query_pairs.get("os").is_some_and(|v| v == "true");
self.scanner = query_pairs.get("scanner").is_some_and(|v| v == "true");
self.decommission = query_pairs.get("decommission").is_some_and(|v| v == "true");
self.healing = query_pairs.get("healing").is_some_and(|v| v == "true");
self.batch_replication = query_pairs.get("batch-replication").is_some_and(|v| v == "true");
self.batch_key_rotation = query_pairs.get("batch-keyrotation").is_some_and(|v| v == "true");
self.batch_expire = query_pairs.get("batch-expire").is_some_and(|v| v == "true");
if query_pairs.get("all").is_some_and(|v| v == "true") {
self.s3 = true;
self.internal = true;
self.storage = true;
self.os = true;
}
self.rebalance = query_pairs.get("rebalance").is_some_and(|v| v == "true");
self.storage = query_pairs.get("storage").is_some_and(|v| v == "true");
self.internal = query_pairs.get("internal").is_some_and(|v| v == "true");
self.only_errors = query_pairs.get("err").is_some_and(|v| v == "true");
self.replication_resync = query_pairs.get("replication-resync").is_some_and(|v| v == "true");
self.bootstrap = query_pairs.get("bootstrap").is_some_and(|v| v == "true");
self.ftp = query_pairs.get("ftp").is_some_and(|v| v == "true");
self.ilm = query_pairs.get("ilm").is_some_and(|v| v == "true");
if let Some(threshold) = query_pairs.get("threshold") {
let duration = parse_duration(threshold)?;
self.threshold = duration;
}
Ok(())
}
}
+180
View File
@@ -0,0 +1,180 @@
// 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::Duration};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use crate::heal_commands::HealResultItem;
#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default)]
pub struct TraceType(u64);
impl TraceType {
// 定义一些常量
pub const OS: TraceType = TraceType(1 << 0);
pub const STORAGE: TraceType = TraceType(1 << 1);
pub const S3: TraceType = TraceType(1 << 2);
pub const INTERNAL: TraceType = TraceType(1 << 3);
pub const SCANNER: TraceType = TraceType(1 << 4);
pub const DECOMMISSION: TraceType = TraceType(1 << 5);
pub const HEALING: TraceType = TraceType(1 << 6);
pub const BATCH_REPLICATION: TraceType = TraceType(1 << 7);
pub const BATCH_KEY_ROTATION: TraceType = TraceType(1 << 8);
pub const BATCH_EXPIRE: TraceType = TraceType(1 << 9);
pub const REBALANCE: TraceType = TraceType(1 << 10);
pub const REPLICATION_RESYNC: TraceType = TraceType(1 << 11);
pub const BOOTSTRAP: TraceType = TraceType(1 << 12);
pub const FTP: TraceType = TraceType(1 << 13);
pub const ILM: TraceType = TraceType(1 << 14);
// MetricsAll must be last.
pub const ALL: TraceType = TraceType((1 << 15) - 1);
pub fn new(t: u64) -> Self {
Self(t)
}
}
impl TraceType {
pub fn contains(&self, x: &TraceType) -> bool {
(self.0 & x.0) == x.0
}
pub fn overlaps(&self, x: &TraceType) -> bool {
(self.0 & x.0) != 0
}
pub fn single_type(&self) -> bool {
todo!()
}
pub fn merge(&mut self, other: &TraceType) {
self.0 |= other.0
}
pub fn set_if(&mut self, b: bool, other: &TraceType) {
if b {
self.0 |= other.0
}
}
pub fn mask(&self) -> u64 {
self.0
}
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct TraceInfo {
#[serde(rename = "type")]
trace_type: u64,
#[serde(rename = "nodename")]
node_name: String,
#[serde(rename = "funcname")]
func_name: String,
#[serde(rename = "time")]
time: DateTime<Utc>,
#[serde(rename = "path")]
path: String,
#[serde(rename = "dur")]
duration: Duration,
#[serde(rename = "bytes", skip_serializing_if = "Option::is_none")]
bytes: Option<i64>,
#[serde(rename = "msg", skip_serializing_if = "Option::is_none")]
message: Option<String>,
#[serde(rename = "error", skip_serializing_if = "Option::is_none")]
error: Option<String>,
#[serde(rename = "custom", skip_serializing_if = "Option::is_none")]
custom: Option<HashMap<String, String>>,
#[serde(rename = "http", skip_serializing_if = "Option::is_none")]
http: Option<TraceHTTPStats>,
#[serde(rename = "healResult", skip_serializing_if = "Option::is_none")]
heal_result: Option<HealResultItem>,
}
impl TraceInfo {
pub fn mask(&self) -> u64 {
TraceType::new(self.trace_type).mask()
}
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct TraceInfoLegacy {
trace_info: TraceInfo,
#[serde(rename = "request")]
req_info: Option<TraceRequestInfo>,
#[serde(rename = "response")]
resp_info: Option<TraceResponseInfo>,
#[serde(rename = "stats")]
call_stats: Option<TraceCallStats>,
#[serde(rename = "storageStats")]
storage_stats: Option<StorageStats>,
#[serde(rename = "osStats")]
os_stats: Option<OSStats>,
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct StorageStats {
path: String,
duration: Duration,
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct OSStats {
path: String,
duration: Duration,
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct TraceHTTPStats {
req_info: TraceRequestInfo,
resp_info: TraceResponseInfo,
call_stats: TraceCallStats,
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct TraceCallStats {
input_bytes: i32,
output_bytes: i32,
latency: Duration,
time_to_first_byte: Duration,
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct TraceRequestInfo {
time: DateTime<Utc>,
proto: String,
method: String,
#[serde(skip_serializing_if = "Option::is_none")]
path: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
raw_query: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
headers: Option<HashMap<String, String>>,
#[serde(skip_serializing_if = "Option::is_none")]
body: Option<Vec<u8>>,
client: String,
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct TraceResponseInfo {
time: DateTime<Utc>,
#[serde(skip_serializing_if = "Option::is_none")]
headers: Option<HashMap<String, String>>,
#[serde(skip_serializing_if = "Option::is_none")]
body: Option<Vec<u8>>,
#[serde(skip_serializing_if = "Option::is_none")]
status_code: Option<i32>,
}
+989
View File
@@ -0,0 +1,989 @@
// 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 serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_json::value::RawValue;
use std::collections::HashMap;
use time::OffsetDateTime;
use crate::BackendInfo;
#[derive(Debug, Serialize, Deserialize, Default, PartialEq, Eq)]
pub enum AccountStatus {
#[serde(rename = "enabled")]
Enabled,
#[serde(rename = "disabled")]
#[default]
Disabled,
}
impl AsRef<str> for AccountStatus {
fn as_ref(&self) -> &str {
match self {
AccountStatus::Enabled => "enabled",
AccountStatus::Disabled => "disabled",
}
}
}
impl TryFrom<&str> for AccountStatus {
type Error = String;
fn try_from(s: &str) -> Result<Self, Self::Error> {
match s {
"enabled" => Ok(AccountStatus::Enabled),
"disabled" => Ok(AccountStatus::Disabled),
_ => Err(format!("invalid account status: {s}")),
}
}
}
#[derive(Debug, Serialize, Deserialize)]
pub enum UserAuthType {
#[serde(rename = "builtin")]
Builtin,
#[serde(rename = "ldap")]
Ldap,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct UserAuthInfo {
#[serde(rename = "type")]
pub auth_type: UserAuthType,
#[serde(rename = "authServer", skip_serializing_if = "Option::is_none")]
pub auth_server: Option<String>,
#[serde(rename = "authServerUserID", skip_serializing_if = "Option::is_none")]
pub auth_server_user_id: Option<String>,
}
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct UserInfo {
#[serde(rename = "userAuthInfo", skip_serializing_if = "Option::is_none")]
pub auth_info: Option<UserAuthInfo>,
#[serde(rename = "secretKey", skip_serializing_if = "Option::is_none")]
pub secret_key: Option<String>,
#[serde(rename = "policyName", skip_serializing_if = "Option::is_none")]
pub policy_name: Option<String>,
#[serde(rename = "status")]
pub status: AccountStatus,
#[serde(rename = "memberOf", skip_serializing_if = "Option::is_none")]
pub member_of: Option<Vec<String>>,
#[serde(rename = "updatedAt")]
pub updated_at: Option<OffsetDateTime>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct AddOrUpdateUserReq {
#[serde(rename = "secretKey")]
pub secret_key: String,
#[serde(rename = "policy", skip_serializing_if = "Option::is_none")]
pub policy: Option<String>,
#[serde(rename = "status")]
pub status: AccountStatus,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct ServiceAccountInfo {
#[serde(rename = "parentUser")]
pub parent_user: String,
#[serde(rename = "accountStatus")]
pub account_status: String,
#[serde(rename = "impliedPolicy")]
pub implied_policy: bool,
#[serde(rename = "accessKey")]
pub access_key: String,
#[serde(rename = "name", skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(rename = "description", skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(rename = "expiration", with = "time::serde::rfc3339::option")]
pub expiration: Option<OffsetDateTime>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct ListServiceAccountsResp {
#[serde(rename = "accounts")]
pub accounts: Vec<ServiceAccountInfo>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct AddServiceAccountReq {
#[serde(rename = "policy", skip_serializing_if = "Option::is_none")]
pub policy: Option<String>,
#[serde(rename = "targetUser", skip_serializing_if = "Option::is_none")]
pub target_user: Option<String>,
#[serde(rename = "accessKey")]
pub access_key: String,
#[serde(rename = "secretKey")]
pub secret_key: String,
#[serde(rename = "name")]
pub name: Option<String>,
#[serde(rename = "description", skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(rename = "expiration", with = "time::serde::rfc3339::option")]
pub expiration: Option<OffsetDateTime>,
}
impl AddServiceAccountReq {
pub fn validate(&self) -> Result<(), String> {
if self.access_key.is_empty() {
return Err("accessKey is empty".to_string());
}
if self.secret_key.is_empty() {
return Err("secretKey is empty".to_string());
}
if self.name.is_none() {
return Err("name is empty".to_string());
}
// TODO: validate
Ok(())
}
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Credentials<'a> {
pub access_key: &'a str,
pub secret_key: &'a str,
#[serde(skip_serializing_if = "Option::is_none")]
pub session_token: Option<&'a str>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(with = "time::serde::rfc3339::option")]
pub expiration: Option<OffsetDateTime>,
}
#[derive(Serialize)]
pub struct AddServiceAccountResp<'a> {
pub credentials: Credentials<'a>,
}
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
pub struct InfoServiceAccountResp {
pub parent_user: String,
pub account_status: String,
pub implied_policy: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub policy: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(with = "time::serde::rfc3339::option")]
pub expiration: Option<OffsetDateTime>,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct UpdateServiceAccountReq {
#[serde(rename = "newPolicy", skip_serializing_if = "Option::is_none")]
pub new_policy: Option<String>,
#[serde(rename = "newSecretKey", skip_serializing_if = "Option::is_none")]
pub new_secret_key: Option<String>,
#[serde(rename = "newStatus", skip_serializing_if = "Option::is_none")]
pub new_status: Option<String>,
#[serde(rename = "newName", skip_serializing_if = "Option::is_none")]
pub new_name: Option<String>,
#[serde(rename = "newDescription", skip_serializing_if = "Option::is_none")]
pub new_description: Option<String>,
#[serde(rename = "newExpiration", skip_serializing_if = "Option::is_none")]
#[serde(with = "time::serde::rfc3339::option")]
pub new_expiration: Option<OffsetDateTime>,
}
impl UpdateServiceAccountReq {
pub fn validate(&self) -> Result<(), String> {
// TODO: validate
Ok(())
}
}
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct AccountInfo {
pub account_name: String,
pub server: BackendInfo,
pub policy: serde_json::Value, // Use iam/policy::parse to parse the result, to be done by the caller.
pub buckets: Vec<BucketAccessInfo>,
}
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct BucketAccessInfo {
pub name: String,
pub size: u64,
pub objects: u64,
pub object_sizes_histogram: HashMap<String, u64>,
pub object_versions_histogram: HashMap<String, u64>,
pub details: Option<BucketDetails>,
pub prefix_usage: HashMap<String, u64>,
#[serde(rename = "expiration", with = "time::serde::rfc3339::option")]
pub created: Option<OffsetDateTime>,
pub access: AccountAccess,
}
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct BucketDetails {
pub versioning: bool,
pub versioning_suspended: bool,
pub locking: bool,
pub replication: bool,
// pub tagging: Option<Tagging>,
}
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct AccountAccess {
pub read: bool,
pub write: bool,
}
/// SRSessionPolicy - represents a session policy to be replicated.
#[derive(Debug, Clone)]
pub struct SRSessionPolicy(Option<Box<RawValue>>);
impl SRSessionPolicy {
pub fn new() -> Self {
SRSessionPolicy(None)
}
pub fn from_json(json: &str) -> Result<Self, serde_json::Error> {
if json == "null" {
Ok(SRSessionPolicy(None))
} else {
let raw_value = serde_json::from_str(json)?;
Ok(SRSessionPolicy(Some(raw_value)))
}
}
pub fn is_null(&self) -> bool {
self.0.is_none()
}
pub fn as_str(&self) -> Option<&str> {
self.0.as_ref().map(|v| v.get())
}
}
impl Default for SRSessionPolicy {
fn default() -> Self {
Self::new()
}
}
impl PartialEq for SRSessionPolicy {
fn eq(&self, other: &Self) -> bool {
self.0.as_ref().map(|v| v.get()) == other.0.as_ref().map(|v| v.get())
}
}
impl Serialize for SRSessionPolicy {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match &self.0 {
Some(raw_value) => raw_value.serialize(serializer),
None => serializer.serialize_none(),
}
}
}
impl<'de> Deserialize<'de> for SRSessionPolicy {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let raw_value: Option<Box<RawValue>> = Option::deserialize(deserializer)?;
Ok(SRSessionPolicy(raw_value))
}
}
/// SRSvcAccCreate - create operation
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SRSvcAccCreate {
pub parent: String,
#[serde(rename = "accessKey")]
pub access_key: String,
#[serde(rename = "secretKey")]
pub secret_key: String,
pub groups: Vec<String>,
pub claims: HashMap<String, serde_json::Value>,
#[serde(rename = "sessionPolicy")]
pub session_policy: SRSessionPolicy,
pub status: String,
pub name: String,
pub description: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub expiration: Option<OffsetDateTime>,
#[serde(rename = "apiVersion", skip_serializing_if = "Option::is_none")]
pub api_version: Option<String>,
}
/// ImportIAMResult - represents the structure iam import response
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ImportIAMResult {
/// Skipped entries while import
/// This could be due to groups, policies etc missing for
/// imported entries. We dont fail hard in this case and
pub skipped: IAMEntities,
/// Removed entries - this mostly happens for policies
/// where empty might be getting imported and that's invalid
pub removed: IAMEntities,
/// Newly added entries
pub added: IAMEntities,
/// Failed entries while import. This would have details of
/// failed entities with respective errors
pub failed: IAMErrEntities,
}
/// IAMEntities - represents different IAM entities
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
pub struct IAMEntities {
/// List of policy names
pub policies: Vec<String>,
/// List of user names
pub users: Vec<String>,
/// List of group names
pub groups: Vec<String>,
/// List of Service Account names
#[serde(rename = "serviceAccounts")]
pub service_accounts: Vec<String>,
/// List of user policies, each entry in map represents list of policies
/// applicable to the user
#[serde(rename = "userPolicies")]
pub user_policies: Vec<HashMap<String, Vec<String>>>,
/// List of group policies, each entry in map represents list of policies
/// applicable to the group
#[serde(rename = "groupPolicies")]
pub group_policies: Vec<HashMap<String, Vec<String>>>,
/// List of STS policies, each entry in map represents list of policies
/// applicable to the STS
#[serde(rename = "stsPolicies")]
pub sts_policies: Vec<HashMap<String, Vec<String>>>,
}
/// IAMErrEntities - represents errored out IAM entries while import with error
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct IAMErrEntities {
/// List of errored out policies with errors
pub policies: Vec<IAMErrEntity>,
/// List of errored out users with errors
pub users: Vec<IAMErrEntity>,
/// List of errored out groups with errors
pub groups: Vec<IAMErrEntity>,
/// List of errored out service accounts with errors
#[serde(rename = "serviceAccounts")]
pub service_accounts: Vec<IAMErrEntity>,
/// List of errored out user policies with errors
#[serde(rename = "userPolicies")]
pub user_policies: Vec<IAMErrPolicyEntity>,
/// List of errored out group policies with errors
#[serde(rename = "groupPolicies")]
pub group_policies: Vec<IAMErrPolicyEntity>,
/// List of errored out STS policies with errors
#[serde(rename = "stsPolicies")]
pub sts_policies: Vec<IAMErrPolicyEntity>,
}
/// IAMErrEntity - represents an errored IAM entity with error details
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IAMErrEntity {
pub name: String,
pub error: String,
}
/// IAMErrPolicyEntity - represents an errored policy entity with error details
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IAMErrPolicyEntity {
pub name: String,
pub policies: Vec<String>,
pub error: String,
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json;
use time::OffsetDateTime;
#[test]
fn test_account_status_default() {
let status = AccountStatus::default();
assert_eq!(status, AccountStatus::Disabled);
}
#[test]
fn test_account_status_as_ref() {
assert_eq!(AccountStatus::Enabled.as_ref(), "enabled");
assert_eq!(AccountStatus::Disabled.as_ref(), "disabled");
}
#[test]
fn test_account_status_try_from_valid() {
assert_eq!(AccountStatus::try_from("enabled").unwrap(), AccountStatus::Enabled);
assert_eq!(AccountStatus::try_from("disabled").unwrap(), AccountStatus::Disabled);
}
#[test]
fn test_account_status_try_from_invalid() {
let result = AccountStatus::try_from("invalid");
assert!(result.is_err());
assert!(result.unwrap_err().contains("invalid account status"));
}
#[test]
fn test_account_status_serialization() {
let enabled = AccountStatus::Enabled;
let disabled = AccountStatus::Disabled;
let enabled_json = serde_json::to_string(&enabled).unwrap();
let disabled_json = serde_json::to_string(&disabled).unwrap();
assert_eq!(enabled_json, "\"enabled\"");
assert_eq!(disabled_json, "\"disabled\"");
}
#[test]
fn test_account_status_deserialization() {
let enabled: AccountStatus = serde_json::from_str("\"enabled\"").unwrap();
let disabled: AccountStatus = serde_json::from_str("\"disabled\"").unwrap();
assert_eq!(enabled, AccountStatus::Enabled);
assert_eq!(disabled, AccountStatus::Disabled);
}
#[test]
fn test_user_auth_type_serialization() {
let builtin = UserAuthType::Builtin;
let ldap = UserAuthType::Ldap;
let builtin_json = serde_json::to_string(&builtin).unwrap();
let ldap_json = serde_json::to_string(&ldap).unwrap();
assert_eq!(builtin_json, "\"builtin\"");
assert_eq!(ldap_json, "\"ldap\"");
}
#[test]
fn test_user_auth_info_creation() {
let auth_info = UserAuthInfo {
auth_type: UserAuthType::Ldap,
auth_server: Some("ldap.example.com".to_string()),
auth_server_user_id: Some("user123".to_string()),
};
assert!(matches!(auth_info.auth_type, UserAuthType::Ldap));
assert_eq!(auth_info.auth_server.unwrap(), "ldap.example.com");
assert_eq!(auth_info.auth_server_user_id.unwrap(), "user123");
}
#[test]
fn test_user_auth_info_serialization() {
let auth_info = UserAuthInfo {
auth_type: UserAuthType::Builtin,
auth_server: None,
auth_server_user_id: None,
};
let json = serde_json::to_string(&auth_info).unwrap();
assert!(json.contains("builtin"));
assert!(!json.contains("authServer"), "None fields should be skipped");
}
#[test]
fn test_user_info_default() {
let user_info = UserInfo::default();
assert!(user_info.auth_info.is_none());
assert!(user_info.secret_key.is_none());
assert!(user_info.policy_name.is_none());
assert_eq!(user_info.status, AccountStatus::Disabled);
assert!(user_info.member_of.is_none());
assert!(user_info.updated_at.is_none());
}
#[test]
fn test_user_info_with_values() {
let now = OffsetDateTime::now_utc();
let user_info = UserInfo {
auth_info: Some(UserAuthInfo {
auth_type: UserAuthType::Builtin,
auth_server: None,
auth_server_user_id: None,
}),
secret_key: Some("secret123".to_string()),
policy_name: Some("ReadOnlyAccess".to_string()),
status: AccountStatus::Enabled,
member_of: Some(vec!["group1".to_string(), "group2".to_string()]),
updated_at: Some(now),
};
assert!(user_info.auth_info.is_some());
assert_eq!(user_info.secret_key.unwrap(), "secret123");
assert_eq!(user_info.policy_name.unwrap(), "ReadOnlyAccess");
assert_eq!(user_info.status, AccountStatus::Enabled);
assert_eq!(user_info.member_of.unwrap().len(), 2);
assert!(user_info.updated_at.is_some());
}
#[test]
fn test_add_or_update_user_req_creation() {
let req = AddOrUpdateUserReq {
secret_key: "newsecret".to_string(),
policy: Some("FullAccess".to_string()),
status: AccountStatus::Enabled,
};
assert_eq!(req.secret_key, "newsecret");
assert_eq!(req.policy.unwrap(), "FullAccess");
assert_eq!(req.status, AccountStatus::Enabled);
}
#[test]
fn test_service_account_info_creation() {
let now = OffsetDateTime::now_utc();
let service_account = ServiceAccountInfo {
parent_user: "admin".to_string(),
account_status: "enabled".to_string(),
implied_policy: true,
access_key: "AKIAIOSFODNN7EXAMPLE".to_string(),
name: Some("test-service".to_string()),
description: Some("Test service account".to_string()),
expiration: Some(now),
};
assert_eq!(service_account.parent_user, "admin");
assert_eq!(service_account.account_status, "enabled");
assert!(service_account.implied_policy);
assert_eq!(service_account.access_key, "AKIAIOSFODNN7EXAMPLE");
assert_eq!(service_account.name.unwrap(), "test-service");
assert!(service_account.expiration.is_some());
}
#[test]
fn test_list_service_accounts_resp_creation() {
let resp = ListServiceAccountsResp {
accounts: vec![
ServiceAccountInfo {
parent_user: "user1".to_string(),
account_status: "enabled".to_string(),
implied_policy: false,
access_key: "KEY1".to_string(),
name: Some("service1".to_string()),
description: None,
expiration: None,
},
ServiceAccountInfo {
parent_user: "user2".to_string(),
account_status: "disabled".to_string(),
implied_policy: true,
access_key: "KEY2".to_string(),
name: Some("service2".to_string()),
description: Some("Second service".to_string()),
expiration: None,
},
],
};
assert_eq!(resp.accounts.len(), 2);
assert_eq!(resp.accounts[0].parent_user, "user1");
assert_eq!(resp.accounts[1].account_status, "disabled");
}
#[test]
fn test_add_service_account_req_validate_success() {
let req = AddServiceAccountReq {
policy: Some("ReadOnlyAccess".to_string()),
target_user: Some("testuser".to_string()),
access_key: "AKIAIOSFODNN7EXAMPLE".to_string(),
secret_key: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY".to_string(),
name: Some("test-service".to_string()),
description: Some("Test service account".to_string()),
expiration: None,
};
let result = req.validate();
assert!(result.is_ok());
}
#[test]
fn test_add_service_account_req_validate_empty_access_key() {
let req = AddServiceAccountReq {
policy: None,
target_user: None,
access_key: "".to_string(),
secret_key: "secret".to_string(),
name: Some("test".to_string()),
description: None,
expiration: None,
};
let result = req.validate();
assert!(result.is_err());
assert!(result.unwrap_err().contains("accessKey is empty"));
}
#[test]
fn test_add_service_account_req_validate_empty_secret_key() {
let req = AddServiceAccountReq {
policy: None,
target_user: None,
access_key: "AKIAIOSFODNN7EXAMPLE".to_string(),
secret_key: "".to_string(),
name: Some("test".to_string()),
description: None,
expiration: None,
};
let result = req.validate();
assert!(result.is_err());
assert!(result.unwrap_err().contains("secretKey is empty"));
}
#[test]
fn test_add_service_account_req_validate_empty_name() {
let req = AddServiceAccountReq {
policy: None,
target_user: None,
access_key: "AKIAIOSFODNN7EXAMPLE".to_string(),
secret_key: "secret".to_string(),
name: None,
description: None,
expiration: None,
};
let result = req.validate();
assert!(result.is_err());
assert!(result.unwrap_err().contains("name is empty"));
}
#[test]
fn test_credentials_serialization() {
let now = OffsetDateTime::now_utc();
let credentials = Credentials {
access_key: "AKIAIOSFODNN7EXAMPLE",
secret_key: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
session_token: Some("session123"),
expiration: Some(now),
};
let json = serde_json::to_string(&credentials).unwrap();
assert!(json.contains("AKIAIOSFODNN7EXAMPLE"));
assert!(json.contains("wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"));
assert!(json.contains("session123"));
}
#[test]
fn test_credentials_without_optional_fields() {
let credentials = Credentials {
access_key: "AKIAIOSFODNN7EXAMPLE",
secret_key: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
session_token: None,
expiration: None,
};
let json = serde_json::to_string(&credentials).unwrap();
assert!(json.contains("AKIAIOSFODNN7EXAMPLE"));
assert!(!json.contains("sessionToken"), "None fields should be skipped");
assert!(!json.contains("expiration"), "None fields should be skipped");
}
#[test]
fn test_add_service_account_resp_creation() {
let credentials = Credentials {
access_key: "AKIAIOSFODNN7EXAMPLE",
secret_key: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
session_token: None,
expiration: None,
};
let resp = AddServiceAccountResp { credentials };
assert_eq!(resp.credentials.access_key, "AKIAIOSFODNN7EXAMPLE");
assert_eq!(resp.credentials.secret_key, "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY");
}
#[test]
fn test_info_service_account_resp_creation() {
let now = OffsetDateTime::now_utc();
let resp = InfoServiceAccountResp {
parent_user: "admin".to_string(),
account_status: "enabled".to_string(),
implied_policy: true,
policy: Some("ReadOnlyAccess".to_string()),
name: Some("test-service".to_string()),
description: Some("Test service account".to_string()),
expiration: Some(now),
};
assert_eq!(resp.parent_user, "admin");
assert_eq!(resp.account_status, "enabled");
assert!(resp.implied_policy);
assert_eq!(resp.policy.unwrap(), "ReadOnlyAccess");
assert_eq!(resp.name.unwrap(), "test-service");
assert!(resp.expiration.is_some());
}
#[test]
fn test_update_service_account_req_validate() {
let req = UpdateServiceAccountReq {
new_policy: Some("FullAccess".to_string()),
new_secret_key: Some("newsecret".to_string()),
new_status: Some("enabled".to_string()),
new_name: Some("updated-service".to_string()),
new_description: Some("Updated description".to_string()),
new_expiration: None,
};
let result = req.validate();
assert!(result.is_ok());
}
#[test]
fn test_account_info_creation() {
use crate::BackendInfo;
let account_info = AccountInfo {
account_name: "testuser".to_string(),
server: BackendInfo::default(),
policy: serde_json::json!({"Version": "2012-10-17"}),
buckets: vec![],
};
assert_eq!(account_info.account_name, "testuser");
assert!(account_info.buckets.is_empty());
assert!(account_info.policy.is_object());
}
#[test]
fn test_bucket_access_info_creation() {
let now = OffsetDateTime::now_utc();
let mut sizes_histogram = HashMap::new();
sizes_histogram.insert("small".to_string(), 100);
sizes_histogram.insert("large".to_string(), 50);
let mut versions_histogram = HashMap::new();
versions_histogram.insert("v1".to_string(), 80);
versions_histogram.insert("v2".to_string(), 70);
let mut prefix_usage = HashMap::new();
prefix_usage.insert("logs/".to_string(), 1000000);
prefix_usage.insert("data/".to_string(), 5000000);
let bucket_info = BucketAccessInfo {
name: "test-bucket".to_string(),
size: 6000000,
objects: 150,
object_sizes_histogram: sizes_histogram,
object_versions_histogram: versions_histogram,
details: Some(BucketDetails {
versioning: true,
versioning_suspended: false,
locking: true,
replication: false,
}),
prefix_usage,
created: Some(now),
access: AccountAccess {
read: true,
write: false,
},
};
assert_eq!(bucket_info.name, "test-bucket");
assert_eq!(bucket_info.size, 6000000);
assert_eq!(bucket_info.objects, 150);
assert_eq!(bucket_info.object_sizes_histogram.len(), 2);
assert_eq!(bucket_info.object_versions_histogram.len(), 2);
assert!(bucket_info.details.is_some());
assert_eq!(bucket_info.prefix_usage.len(), 2);
assert!(bucket_info.created.is_some());
assert!(bucket_info.access.read);
assert!(!bucket_info.access.write);
}
#[test]
fn test_bucket_details_creation() {
let details = BucketDetails {
versioning: true,
versioning_suspended: false,
locking: true,
replication: true,
};
assert!(details.versioning);
assert!(!details.versioning_suspended);
assert!(details.locking);
assert!(details.replication);
}
#[test]
fn test_account_access_creation() {
let read_only = AccountAccess {
read: true,
write: false,
};
let full_access = AccountAccess { read: true, write: true };
let no_access = AccountAccess {
read: false,
write: false,
};
assert!(read_only.read && !read_only.write);
assert!(full_access.read && full_access.write);
assert!(!no_access.read && !no_access.write);
}
#[test]
fn test_serialization_deserialization_roundtrip() {
let user_info = UserInfo {
auth_info: Some(UserAuthInfo {
auth_type: UserAuthType::Ldap,
auth_server: Some("ldap.example.com".to_string()),
auth_server_user_id: Some("user123".to_string()),
}),
secret_key: Some("secret123".to_string()),
policy_name: Some("ReadOnlyAccess".to_string()),
status: AccountStatus::Enabled,
member_of: Some(vec!["group1".to_string()]),
updated_at: None,
};
let json = serde_json::to_string(&user_info).unwrap();
let deserialized: UserInfo = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.secret_key.unwrap(), "secret123");
assert_eq!(deserialized.policy_name.unwrap(), "ReadOnlyAccess");
assert_eq!(deserialized.status, AccountStatus::Enabled);
assert_eq!(deserialized.member_of.unwrap().len(), 1);
}
#[test]
fn test_debug_format_all_structures() {
let account_status = AccountStatus::Enabled;
let user_auth_type = UserAuthType::Builtin;
let user_info = UserInfo::default();
let service_account = ServiceAccountInfo {
parent_user: "test".to_string(),
account_status: "enabled".to_string(),
implied_policy: false,
access_key: "key".to_string(),
name: None,
description: None,
expiration: None,
};
// Test that all structures can be formatted with Debug
assert!(!format!("{account_status:?}").is_empty());
assert!(!format!("{user_auth_type:?}").is_empty());
assert!(!format!("{user_info:?}").is_empty());
assert!(!format!("{service_account:?}").is_empty());
}
#[test]
fn test_memory_efficiency() {
// Test that structures don't use excessive memory
assert!(std::mem::size_of::<AccountStatus>() < 100);
assert!(std::mem::size_of::<UserAuthType>() < 100);
assert!(std::mem::size_of::<UserInfo>() < 2000);
assert!(std::mem::size_of::<ServiceAccountInfo>() < 2000);
assert!(std::mem::size_of::<AccountAccess>() < 100);
}
#[test]
fn test_edge_cases() {
// Test empty strings and edge cases
let req = AddServiceAccountReq {
policy: Some("".to_string()),
target_user: Some("".to_string()),
access_key: "valid_key".to_string(),
secret_key: "valid_secret".to_string(),
name: Some("valid_name".to_string()),
description: Some("".to_string()),
expiration: None,
};
// Should still validate successfully with empty optional strings
assert!(req.validate().is_ok());
// Test very long strings
let long_string = "a".repeat(1000);
let long_req = AddServiceAccountReq {
policy: Some(long_string.clone()),
target_user: Some(long_string.clone()),
access_key: long_string.clone(),
secret_key: long_string.clone(),
name: Some(long_string.clone()),
description: Some(long_string),
expiration: None,
};
assert!(long_req.validate().is_ok());
}
}
+51
View File
@@ -0,0 +1,51 @@
// 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::time::Duration;
pub fn parse_duration(s: &str) -> Result<Duration, String> {
// Implement your own duration parsing logic here
// For example, you could use the humantime crate or a custom parser
humantime::parse_duration(s).map_err(|e| e.to_string())
}
#[cfg(test)]
mod test {
use std::time::Duration;
use super::parse_duration;
#[test]
fn test_parse_dur() {
let s = String::from("3s");
let dur = parse_duration(&s);
println!("{dur:?}");
assert_eq!(Ok(Duration::from_secs(3)), dur);
let s = String::from("3ms");
let dur = parse_duration(&s);
println!("{dur:?}");
assert_eq!(Ok(Duration::from_millis(3)), dur);
let s = String::from("3m");
let dur = parse_duration(&s);
println!("{dur:?}");
assert_eq!(Ok(Duration::from_secs(3 * 60)), dur);
let s = String::from("3h");
let dur = parse_duration(&s);
println!("{dur:?}");
assert_eq!(Ok(Duration::from_secs(3 * 60 * 60)), dur);
}
}