mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-20 03:22:18 +00:00
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:
@@ -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>,
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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
@@ -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::*;
|
||||
@@ -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>,
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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>,
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
@@ -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>,
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user