mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-25 05:26:50 +00:00
fix(ecstore): persist unresolved decommission entries (#6415)
* fix(ecstore): persist unresolved decommission entries * fix(ecstore): type decommission completion result * fix(ecstore): allow intentional decommission listing signatures under strict clippy The sftp/swift feature-matrix clippy gates run with -D warnings and flag the unresolved-entry resolver (large Err payload by design, 8 context parameters) and the decommission listing driver (9 args). Document why and align with the existing decommission_entry precedent.
This commit is contained in:
@@ -168,6 +168,19 @@ pub const DEFAULT_DATA_MOVEMENT_PART_CHECKSUMS_FLEET_CONFIRMED: bool = false;
|
|||||||
const _: () = assert!(!DEFAULT_DATA_MOVEMENT_PART_CHECKSUMS_WRITE);
|
const _: () = assert!(!DEFAULT_DATA_MOVEMENT_PART_CHECKSUMS_WRITE);
|
||||||
const _: () = assert!(!DEFAULT_DATA_MOVEMENT_PART_CHECKSUMS_FLEET_CONFIRMED);
|
const _: () = assert!(!DEFAULT_DATA_MOVEMENT_PART_CHECKSUMS_FLEET_CONFIRMED);
|
||||||
|
|
||||||
|
/// Request writing pool metadata version 2.
|
||||||
|
///
|
||||||
|
/// This remains ineffective until [`ENV_POOL_META_V2_FLEET_CONFIRMED`] is also enabled.
|
||||||
|
pub const ENV_POOL_META_V2_WRITE: &str = "RUSTFS_POOL_META_V2_WRITE";
|
||||||
|
pub const DEFAULT_POOL_META_V2_WRITE: bool = false;
|
||||||
|
|
||||||
|
/// Operator-attested confirmation that every pool metadata reader and writer understands version 2.
|
||||||
|
pub const ENV_POOL_META_V2_FLEET_CONFIRMED: &str = "RUSTFS_POOL_META_V2_FLEET_CONFIRMED";
|
||||||
|
pub const DEFAULT_POOL_META_V2_FLEET_CONFIRMED: bool = false;
|
||||||
|
|
||||||
|
const _: () = assert!(!DEFAULT_POOL_META_V2_WRITE);
|
||||||
|
const _: () = assert!(!DEFAULT_POOL_META_V2_FLEET_CONFIRMED);
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// Concurrent Request Fix - Timeout and Backpressure Configuration
|
// Concurrent Request Fix - Timeout and Backpressure Configuration
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
@@ -736,4 +749,10 @@ mod remote_version_state_tests {
|
|||||||
"RUSTFS_OBJECT_TRANSACTION_FENCING_FLEET_CONFIRMED"
|
"RUSTFS_OBJECT_TRANSACTION_FENCING_FLEET_CONFIRMED"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pool_meta_v2_gate_uses_stable_environment_names() {
|
||||||
|
assert_eq!(super::ENV_POOL_META_V2_WRITE, "RUSTFS_POOL_META_V2_WRITE");
|
||||||
|
assert_eq!(super::ENV_POOL_META_V2_FLEET_CONFIRMED, "RUSTFS_POOL_META_V2_FLEET_CONFIRMED");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -243,8 +243,8 @@ pub mod cache {
|
|||||||
|
|
||||||
pub mod capacity {
|
pub mod capacity {
|
||||||
pub use crate::core::pools::{
|
pub use crate::core::pools::{
|
||||||
PoolDecommissionInfo, PoolStatus, get_total_usable_capacity, get_total_usable_capacity_free, path2_bucket_object,
|
DecommissionUnresolvedEntry, PoolDecommissionInfo, PoolStatus, get_total_usable_capacity, get_total_usable_capacity_free,
|
||||||
path2_bucket_object_with_base_path,
|
path2_bucket_object, path2_bucket_object_with_base_path,
|
||||||
};
|
};
|
||||||
pub use crate::store::utils::is_reserved_or_invalid_bucket;
|
pub use crate::store::utils::is_reserved_or_invalid_bucket;
|
||||||
}
|
}
|
||||||
|
|||||||
+1457
-156
File diff suppressed because it is too large
Load Diff
@@ -637,7 +637,7 @@ mod tests {
|
|||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
bucket::replication::{ReplicationState, ReplicationStatusType, replication_statuses_map},
|
bucket::replication::{ReplicationState, ReplicationStatusType, replication_statuses_map},
|
||||||
core::pools::{POOL_META_FORMAT, POOL_META_VERSION, PoolDecommissionInfo, PoolMeta, PoolStatus},
|
core::pools::{POOL_META_VERSION, PoolDecommissionInfo, PoolMeta, PoolStatus},
|
||||||
disk::endpoint::Endpoint,
|
disk::endpoint::Endpoint,
|
||||||
error::{Error, Result, StorageError},
|
error::{Error, Result, StorageError},
|
||||||
io_support::rio::{WritePlan, compression_metadata_value},
|
io_support::rio::{WritePlan, compression_metadata_value},
|
||||||
@@ -651,7 +651,6 @@ mod tests {
|
|||||||
range::HTTPRangeSpec,
|
range::HTTPRangeSpec,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use byteorder::{LittleEndian, WriteBytesExt};
|
|
||||||
#[cfg(feature = "test-util")]
|
#[cfg(feature = "test-util")]
|
||||||
use futures::{StreamExt as _, TryStreamExt as _};
|
use futures::{StreamExt as _, TryStreamExt as _};
|
||||||
use http::HeaderMap;
|
use http::HeaderMap;
|
||||||
@@ -681,13 +680,7 @@ mod tests {
|
|||||||
use tokio_util::sync::CancellationToken;
|
use tokio_util::sync::CancellationToken;
|
||||||
|
|
||||||
fn startup_pool_meta_payload(meta: &PoolMeta) -> Vec<u8> {
|
fn startup_pool_meta_payload(meta: &PoolMeta) -> Vec<u8> {
|
||||||
let mut data = Vec::new();
|
meta.encode_config_data_for_test().expect("pool metadata should encode")
|
||||||
data.write_u16::<LittleEndian>(POOL_META_FORMAT)
|
|
||||||
.expect("pool metadata format should encode");
|
|
||||||
data.write_u16::<LittleEndian>(POOL_META_VERSION)
|
|
||||||
.expect("pool metadata version should encode");
|
|
||||||
data.extend(rmp_serde::to_vec(meta).expect("legacy pool metadata payload should encode"));
|
|
||||||
data
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|||||||
@@ -31,6 +31,15 @@ failure pattern reported in rustfs/rustfs#4304.
|
|||||||
> older build is not supported: older readers ignore the sidecar and can
|
> older build is not supported: older readers ignore the sidecar and can
|
||||||
> report an object checksum in place of the requested part checksum.
|
> report an object checksum in place of the requested part checksum.
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
> Writing pool metadata version 2 remains inactive unless both
|
||||||
|
> `RUSTFS_POOL_META_V2_WRITE=true` and
|
||||||
|
> `RUSTFS_POOL_META_V2_FLEET_CONFIRMED=true`. Leave either setting disabled
|
||||||
|
> until every node that can read or write `pool.bin` supports version 2. Once a node
|
||||||
|
> observes or writes version 2 it will not downgrade the file, and older
|
||||||
|
> binaries or rollback builds cannot read it. Unresolved decommission entries
|
||||||
|
> fail closed instead of being written in the version 1 format.
|
||||||
|
|
||||||
## TL;DR
|
## TL;DR
|
||||||
|
|
||||||
- **Rolling restart (no downtime):** restart **one node at a time**, and wait
|
- **Rolling restart (no downtime):** restart **one node at a time**, and wait
|
||||||
|
|||||||
@@ -16,7 +16,8 @@
|
|||||||
|
|
||||||
use super::storage_api::admin_usecase::admin::get_server_info;
|
use super::storage_api::admin_usecase::admin::get_server_info;
|
||||||
use super::storage_api::admin_usecase::capacity::{
|
use super::storage_api::admin_usecase::capacity::{
|
||||||
PoolDecommissionInfo, PoolStatus, RebalStatus, get_total_usable_capacity, get_total_usable_capacity_free,
|
DecommissionUnresolvedEntry, PoolDecommissionInfo, PoolStatus, RebalStatus, get_total_usable_capacity,
|
||||||
|
get_total_usable_capacity_free,
|
||||||
};
|
};
|
||||||
use super::storage_api::admin_usecase::contract::StorageAdminApi;
|
use super::storage_api::admin_usecase::contract::StorageAdminApi;
|
||||||
use super::storage_api::admin_usecase::contract::bucket::{BucketOperations as _, BucketOptions};
|
use super::storage_api::admin_usecase::contract::bucket::{BucketOperations as _, BucketOptions};
|
||||||
@@ -107,6 +108,8 @@ pub struct AdminPoolDecommissionInfo {
|
|||||||
pub bytes_failed: usize,
|
pub bytes_failed: usize,
|
||||||
#[serde(rename = "waitingReason")]
|
#[serde(rename = "waitingReason")]
|
||||||
pub waiting_reason: Option<String>,
|
pub waiting_reason: Option<String>,
|
||||||
|
#[serde(rename = "unresolvedEntries", skip_serializing_if = "Vec::is_empty")]
|
||||||
|
pub unresolved_entries: Vec<DecommissionUnresolvedEntry>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, serde::Serialize)]
|
#[derive(Debug, Clone, serde::Serialize)]
|
||||||
@@ -619,6 +622,7 @@ impl DefaultAdminUsecase {
|
|||||||
bytes_done: info.bytes_done,
|
bytes_done: info.bytes_done,
|
||||||
bytes_failed: info.bytes_failed,
|
bytes_failed: info.bytes_failed,
|
||||||
waiting_reason,
|
waiting_reason,
|
||||||
|
unresolved_entries: info.unresolved_entries,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -676,7 +680,7 @@ impl DefaultAdminUsecase {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::super::storage_api::admin_usecase::capacity::{PoolDecommissionInfo, PoolStatus};
|
use super::super::storage_api::admin_usecase::capacity::{DecommissionUnresolvedEntry, PoolDecommissionInfo, PoolStatus};
|
||||||
use super::*;
|
use super::*;
|
||||||
use time::OffsetDateTime;
|
use time::OffsetDateTime;
|
||||||
use tracing_subscriber::{Layer, Registry, layer::Context, prelude::*};
|
use tracing_subscriber::{Layer, Registry, layer::Context, prelude::*};
|
||||||
@@ -987,6 +991,17 @@ mod tests {
|
|||||||
items_decommission_failed: 1,
|
items_decommission_failed: 1,
|
||||||
bytes_done: 1024,
|
bytes_done: 1024,
|
||||||
bytes_failed: 64,
|
bytes_failed: 64,
|
||||||
|
unresolved_entries: vec![DecommissionUnresolvedEntry {
|
||||||
|
bucket: "bucket-a".to_string(),
|
||||||
|
object: "prefix/unresolved.txt".to_string(),
|
||||||
|
pool_index: 3,
|
||||||
|
set_index: 1,
|
||||||
|
source_generation: OffsetDateTime::UNIX_EPOCH,
|
||||||
|
candidate_count: 2,
|
||||||
|
disk_error_count: 1,
|
||||||
|
observed_at: OffsetDateTime::UNIX_EPOCH,
|
||||||
|
reason: "metadata_resolution_failed".to_string(),
|
||||||
|
}],
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
@@ -1010,6 +1025,13 @@ mod tests {
|
|||||||
assert_eq!(value["decommissionInfo"]["objectsDecommissionedFailed"], 1);
|
assert_eq!(value["decommissionInfo"]["objectsDecommissionedFailed"], 1);
|
||||||
assert_eq!(value["decommissionInfo"]["bytesDecommissioned"], 1024);
|
assert_eq!(value["decommissionInfo"]["bytesDecommissioned"], 1024);
|
||||||
assert_eq!(value["decommissionInfo"]["bytesDecommissionedFailed"], 64);
|
assert_eq!(value["decommissionInfo"]["bytesDecommissionedFailed"], 64);
|
||||||
|
assert_eq!(value["decommissionInfo"]["unresolvedEntries"][0]["bucket"], "bucket-a");
|
||||||
|
assert_eq!(value["decommissionInfo"]["unresolvedEntries"][0]["object"], "prefix/unresolved.txt");
|
||||||
|
assert_eq!(
|
||||||
|
value["decommissionInfo"]["unresolvedEntries"][0]["sourceGeneration"],
|
||||||
|
"1970-01-01T00:00:00Z"
|
||||||
|
);
|
||||||
|
assert_eq!(value["decommissionInfo"]["unresolvedEntries"][0]["reason"], "metadata_resolution_failed");
|
||||||
assert_eq!(value["decommissionInfo"]["waitingReason"], "queued");
|
assert_eq!(value["decommissionInfo"]["waitingReason"], "queued");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ pub(crate) mod admin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) mod capacity {
|
pub(crate) mod capacity {
|
||||||
|
pub(crate) type DecommissionUnresolvedEntry = crate::storage::storage_api::ecstore_capacity::DecommissionUnresolvedEntry;
|
||||||
pub(crate) type PoolDecommissionInfo = crate::storage::storage_api::ecstore_capacity::PoolDecommissionInfo;
|
pub(crate) type PoolDecommissionInfo = crate::storage::storage_api::ecstore_capacity::PoolDecommissionInfo;
|
||||||
pub(crate) type PoolStatus = crate::storage::storage_api::ecstore_capacity::PoolStatus;
|
pub(crate) type PoolStatus = crate::storage::storage_api::ecstore_capacity::PoolStatus;
|
||||||
pub(crate) type RebalStatus = crate::storage::storage_api::ecstore_rebalance::RebalStatus;
|
pub(crate) type RebalStatus = crate::storage::storage_api::ecstore_rebalance::RebalStatus;
|
||||||
|
|||||||
@@ -399,7 +399,7 @@ pub(crate) mod ecstore_bucket {
|
|||||||
|
|
||||||
pub(crate) mod ecstore_capacity {
|
pub(crate) mod ecstore_capacity {
|
||||||
pub(crate) use rustfs_ecstore::api::capacity::{
|
pub(crate) use rustfs_ecstore::api::capacity::{
|
||||||
PoolDecommissionInfo, PoolStatus, get_total_usable_capacity, get_total_usable_capacity_free,
|
DecommissionUnresolvedEntry, PoolDecommissionInfo, PoolStatus, get_total_usable_capacity, get_total_usable_capacity_free,
|
||||||
is_reserved_or_invalid_bucket,
|
is_reserved_or_invalid_bucket,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user