mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-21 11:56:38 +00:00
refactor(metrics): migrate scanner report timestamps to jiff (#5710)
* refactor(metrics): migrate scanner report timestamps to jiff Co-Authored-By: heihutu <heihutu@gmail.com> * refactor(madmin): migrate admin timestamps to jiff (#5712) Co-authored-by: heihutu <heihutu@gmail.com> * refactor(storage): migrate RPC DTO timestamps to jiff (#5713) Co-authored-by: heihutu <heihutu@gmail.com> --------- Co-authored-by: heihutu <heihutu@gmail.com>
This commit is contained in:
@@ -39,6 +39,7 @@ use crate::server::{ADMIN_PREFIX, RemoteAddr};
|
||||
use crate::storage::storage_api::lock_bucket_targets_metadata;
|
||||
use http::{HeaderMap, HeaderValue, Uri};
|
||||
use hyper::{Method, StatusCode};
|
||||
use jiff::Timestamp;
|
||||
use matchit::Params;
|
||||
use rustfs_config::MAX_ADMIN_REQUEST_BODY_SIZE;
|
||||
use rustfs_credentials::Credentials;
|
||||
@@ -91,7 +92,7 @@ struct RemoteTargetCredentialsRequest {
|
||||
#[serde(rename = "secretKey")]
|
||||
secret_key: String,
|
||||
session_token: Option<String>,
|
||||
expiration: Option<chrono::DateTime<chrono::Utc>>,
|
||||
expiration: Option<Timestamp>,
|
||||
}
|
||||
|
||||
impl From<RemoteTargetCredentialsRequest> for TargetCredentials {
|
||||
@@ -1050,8 +1051,9 @@ impl Operation for ReplicationMrfHandler {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{
|
||||
REMOTE_TARGET_UNSUPPORTED_FIELDS, REMOTE_TARGET_WRITABLE_FIELDS, RemoteTargetRequest, SUPPORTED_REMOTE_TARGET_API,
|
||||
build_mrf_response, extract_query_params, unique_replication_peers, validate_remote_target_tls_settings,
|
||||
REMOTE_TARGET_UNSUPPORTED_FIELDS, REMOTE_TARGET_WRITABLE_FIELDS, RemoteTargetCredentialsRequest, RemoteTargetRequest,
|
||||
SUPPORTED_REMOTE_TARGET_API, build_mrf_response, extract_query_params, unique_replication_peers,
|
||||
validate_remote_target_tls_settings,
|
||||
};
|
||||
use crate::admin::storage_api::bucket::target::BucketTarget;
|
||||
use crate::admin::storage_api::replication::{BucketStats, DurableMrfBacklog, MrfOpKind, MrfReplicateEntry};
|
||||
@@ -1350,6 +1352,23 @@ mod tests {
|
||||
assert!(err.to_string().contains("credentials.secretKey is required"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remote_target_credentials_expiration_json_remains_rfc3339() {
|
||||
let credentials: RemoteTargetCredentialsRequest = serde_json::from_value(serde_json::json!({
|
||||
"accessKey": "access",
|
||||
"secretKey": "secret",
|
||||
"expiration": "2026-01-01T00:00:00Z"
|
||||
}))
|
||||
.expect("credentials expiration should deserialize from RFC3339 JSON");
|
||||
let credentials = crate::admin::storage_api::bucket::target::Credentials::from(credentials);
|
||||
let expiration = credentials.expiration.expect("expiration should be preserved");
|
||||
|
||||
assert_eq!(
|
||||
serde_json::to_value(expiration).expect("expiration should serialize to JSON"),
|
||||
serde_json::json!("2026-01-01T00:00:00Z")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remote_target_request_rejects_unimplemented_fields() {
|
||||
for (field, value) in [
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
use crate::startup_background::{heal_enabled_from_env, scanner_enabled_from_env};
|
||||
use crate::storage::storage_api::runtime_sources_consumer::EndpointServerPools;
|
||||
use jiff::Timestamp;
|
||||
use rmp_serde::Deserializer;
|
||||
use rustfs_common::heal_channel::HealScanMode;
|
||||
use rustfs_heal::HealOperationsSnapshot;
|
||||
@@ -28,6 +29,31 @@ const NODE_HEAL_STATUS_VERSION: u8 = 1;
|
||||
const NODE_HEAL_STATUS_MAX_SIZE: usize = 64 * 1024;
|
||||
const HEAL_TOPOLOGY_FINGERPRINT_DOMAIN: &[u8] = b"rustfs-heal-topology-v1\0";
|
||||
|
||||
fn chrono_to_jiff_timestamp(timestamp: chrono::DateTime<chrono::Utc>) -> Timestamp {
|
||||
let seconds = timestamp.timestamp();
|
||||
let nanoseconds = match i32::try_from(timestamp.timestamp_subsec_nanos()) {
|
||||
Ok(nanoseconds) => nanoseconds,
|
||||
Err(_) => {
|
||||
return if seconds < 0 { Timestamp::MIN } else { Timestamp::MAX };
|
||||
}
|
||||
};
|
||||
|
||||
match Timestamp::new(seconds, nanoseconds) {
|
||||
Ok(timestamp) => timestamp,
|
||||
Err(_) => {
|
||||
if seconds < 0 {
|
||||
Timestamp::MIN
|
||||
} else {
|
||||
Timestamp::MAX
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn jiff_to_chrono_datetime(timestamp: Timestamp) -> chrono::DateTime<chrono::Utc> {
|
||||
chrono::DateTime::<chrono::Utc>::from(std::time::SystemTime::from(timestamp))
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(crate) struct HealControlCoordinator {
|
||||
pub grid_host: String,
|
||||
@@ -156,7 +182,7 @@ pub(crate) struct NodeHealProgress {
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase", deny_unknown_fields)]
|
||||
struct NodeHealInfo {
|
||||
bitrot_start_time: Option<chrono::DateTime<chrono::Utc>>,
|
||||
bitrot_start_time: Option<Timestamp>,
|
||||
bitrot_start_cycle: u64,
|
||||
current_scan_mode: HealScanMode,
|
||||
}
|
||||
@@ -164,7 +190,7 @@ struct NodeHealInfo {
|
||||
impl From<BackgroundHealInfo> for NodeHealInfo {
|
||||
fn from(info: BackgroundHealInfo) -> Self {
|
||||
Self {
|
||||
bitrot_start_time: info.bitrot_start_time,
|
||||
bitrot_start_time: info.bitrot_start_time.map(chrono_to_jiff_timestamp),
|
||||
bitrot_start_cycle: info.bitrot_start_cycle,
|
||||
current_scan_mode: info.current_scan_mode,
|
||||
}
|
||||
@@ -174,7 +200,7 @@ impl From<BackgroundHealInfo> for NodeHealInfo {
|
||||
impl From<NodeHealInfo> for BackgroundHealInfo {
|
||||
fn from(info: NodeHealInfo) -> Self {
|
||||
Self {
|
||||
bitrot_start_time: info.bitrot_start_time,
|
||||
bitrot_start_time: info.bitrot_start_time.map(jiff_to_chrono_datetime),
|
||||
bitrot_start_cycle: info.bitrot_start_cycle,
|
||||
current_scan_mode: info.current_scan_mode,
|
||||
}
|
||||
@@ -213,7 +239,7 @@ impl NodeHealStatusSnapshot {
|
||||
|
||||
pub(crate) fn info(&self) -> BackgroundHealInfo {
|
||||
BackgroundHealInfo {
|
||||
bitrot_start_time: self.info.bitrot_start_time,
|
||||
bitrot_start_time: self.info.bitrot_start_time.map(jiff_to_chrono_datetime),
|
||||
bitrot_start_cycle: self.info.bitrot_start_cycle,
|
||||
current_scan_mode: self.info.current_scan_mode,
|
||||
}
|
||||
@@ -270,6 +296,7 @@ mod tests {
|
||||
Endpoint,
|
||||
ecstore_layout::{EndpointServerPools, Endpoints, PoolEndpoints},
|
||||
};
|
||||
use chrono::SecondsFormat;
|
||||
use rustfs_heal::HealOperationsSnapshot;
|
||||
use rustfs_scanner::scanner::BackgroundHealInfo;
|
||||
|
||||
@@ -461,6 +488,54 @@ mod tests {
|
||||
assert_eq!(decoded.operations.queue_length, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn node_heal_status_timestamp_json_remains_rfc3339() {
|
||||
let fixture = serde_json::json!({
|
||||
"version": 1,
|
||||
"servicesEnabled": true,
|
||||
"initialized": true,
|
||||
"info": {"bitrotStartTime": "2023-11-14T22:13:20.123456Z", "bitrotStartCycle": 9, "currentScanMode": 1},
|
||||
"operations": {
|
||||
"queueLength": 2, "activeTasks": 1, "retryingTasks": 0,
|
||||
"queuedByPriority": {"low": 0, "normal": 2, "high": 0, "urgent": 0},
|
||||
"activeByPriority": {"low": 0, "normal": 0, "high": 1, "urgent": 0},
|
||||
"retryingByPriority": {"low": 0, "normal": 0, "high": 0, "urgent": 0},
|
||||
"queuedBySource": {"scanner": 2, "admin": 0, "autoHeal": 0, "internal": 0, "readRepair": 0},
|
||||
"activeBySource": {"scanner": 0, "admin": 1, "autoHeal": 0, "internal": 0, "readRepair": 0},
|
||||
"retryingBySource": {"scanner": 0, "admin": 0, "autoHeal": 0, "internal": 0, "readRepair": 0}
|
||||
},
|
||||
"progress": null
|
||||
});
|
||||
let encoded = rmp_serde::to_vec_named(&fixture).expect("fixture should encode");
|
||||
let decoded = decode_node_heal_status(&encoded).expect("timestamp fixture should decode");
|
||||
let info = decoded.info();
|
||||
|
||||
assert_eq!(
|
||||
info.bitrot_start_time
|
||||
.expect("bitrot start time should be preserved")
|
||||
.to_rfc3339_opts(SecondsFormat::Micros, true),
|
||||
"2023-11-14T22:13:20.123456Z"
|
||||
);
|
||||
|
||||
let started_at = chrono::DateTime::parse_from_rfc3339("2023-11-14T22:13:20.123456Z")
|
||||
.expect("fixture timestamp should parse")
|
||||
.with_timezone(&chrono::Utc);
|
||||
let snapshot = NodeHealStatusSnapshot::for_test(
|
||||
true,
|
||||
true,
|
||||
BackgroundHealInfo {
|
||||
bitrot_start_time: Some(started_at),
|
||||
bitrot_start_cycle: 9,
|
||||
current_scan_mode: rustfs_common::heal_channel::HealScanMode::Deep,
|
||||
},
|
||||
HealOperationsSnapshot::default(),
|
||||
None,
|
||||
);
|
||||
let encoded = encode_node_heal_status(&snapshot).expect("snapshot should encode");
|
||||
let encoded_json: serde_json::Value = rmp_serde::from_slice(&encoded).expect("encoded snapshot should decode as JSON");
|
||||
assert_eq!(encoded_json["info"]["bitrotStartTime"], serde_json::json!("2023-11-14T22:13:20.123456Z"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn node_heal_status_rejects_nested_unknown_trailing_truncated_and_oversized_data() {
|
||||
let mut fixture = serde_json::json!({
|
||||
|
||||
Reference in New Issue
Block a user