fix(ecstore): recover data usage snapshots safely (#4979)

This commit is contained in:
Zhengchao An
2026-07-18 03:02:16 +08:00
committed by GitHub
parent 4589148f48
commit 87128682b0
10 changed files with 693 additions and 90 deletions
+48 -2
View File
@@ -279,6 +279,17 @@ where
Ok(data)
}
/// Read an existing config object without treating an empty payload as absent.
/// Callers that validate their own payload format need to distinguish corruption
/// from `ConfigNotFound`.
pub(crate) async fn read_config_preserve_empty<S>(api: Arc<S>, file: &str) -> Result<Vec<u8>>
where
S: EcstoreObjectIO,
{
let (data, _obj) = read_config_with_metadata_inner(api, file, &ObjectOptions::default(), true).await?;
Ok(data)
}
pub async fn read_config_no_lock<S>(api: Arc<S>, file: &str) -> Result<Vec<u8>>
where
S: ObjectIO<
@@ -304,6 +315,26 @@ where
}
pub async fn read_config_with_metadata<S>(api: Arc<S>, file: &str, opts: &ObjectOptions) -> Result<(Vec<u8>, ObjectInfo)>
where
S: ObjectIO<
Error = Error,
RangeSpec = HTTPRangeSpec,
HeaderMap = HeaderMap,
ObjectOptions = ObjectOptions,
ObjectInfo = ObjectInfo,
GetObjectReader = GetObjectReader,
PutObjectReader = PutObjReader,
>,
{
read_config_with_metadata_inner(api, file, opts, false).await
}
async fn read_config_with_metadata_inner<S>(
api: Arc<S>,
file: &str,
opts: &ObjectOptions,
preserve_empty: bool,
) -> Result<(Vec<u8>, ObjectInfo)>
where
S: ObjectIO<
Error = Error,
@@ -330,7 +361,7 @@ where
let data = rd.read_all().await?;
if data.is_empty() {
if data.is_empty() && !preserve_empty {
return Err(Error::ConfigNotFound);
}
@@ -1596,7 +1627,7 @@ where
mod tests {
use super::{
configs_semantically_equal, decode_server_config_blob, encode_server_config_blob, is_standard_object_server_config,
read_config_with_metadata, storage_class_kvs_mut,
read_config, read_config_preserve_empty, read_config_with_metadata, storage_class_kvs_mut,
};
use crate::config::{audit, notify, oidc};
use crate::disk::endpoint::Endpoint;
@@ -3059,6 +3090,21 @@ mod tests {
}
}
#[tokio::test]
async fn read_config_preserve_empty_distinguishes_empty_object() {
let store = Arc::new(RecoveryMockStore::new(RecoveryReadState::Blob(Vec::new()), None));
let err = read_config(store.clone(), "config/empty.json")
.await
.expect_err("the existing config contract treats empty objects as missing");
assert!(matches!(err, Error::ConfigNotFound));
let data = read_config_preserve_empty(store, "config/empty.json")
.await
.expect("payload-validating callers must observe the empty object");
assert!(data.is_empty());
}
#[async_trait::async_trait]
impl StorageAdminApi for RecoveryMockStore {
type BackendInfo = rustfs_madmin::BackendInfo;