mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-05 04:47:43 +00:00
a63b79004c
* fix(scanner): make distributed usage cycles authoritative * fix(scanner): close distributed refresh races * fix(config): align scanner reload integration * fix(admin): scope config test helpers * fix(scanner): harden distributed usage convergence * fix(scanner): preserve rolling activity compatibility * fix(admin): expose non-secret optional config values * fix(scanner): acknowledge distributed dirty usage * fix(ecstore): make bucket mutations cancellation safe * fix(scanner): preserve pending dirty acknowledgements * test(obs): account for superseded scanner metric * fix(api): reject excess detached bucket mutations * test: close scanner convergence coverage gaps * fix(scanner): make path tracking cleanup one-shot --------- Co-authored-by: Henry Guo <marshawcoco@users.noreply.github.com> Co-authored-by: houseme <housemecn@gmail.com>
133 lines
5.1 KiB
Rust
133 lines
5.1 KiB
Rust
// 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 rustfs_data_usage::{BucketUsageInfo, DATA_USAGE_OBJECT_NAME, DataUsageInfo};
|
|
use std::time::{Duration, SystemTime};
|
|
|
|
use crate::TestECStoreEnv;
|
|
use crate::ecstore_test_compat::fixture::{
|
|
BUCKET_META_PREFIX, delete_config, load_data_usage_from_backend, load_data_usage_from_backend_cached, read_config,
|
|
remove_bucket_usage_from_backend, save_config, store_data_usage_in_backend,
|
|
};
|
|
|
|
fn snapshot(bucket: &str, last_update: SystemTime) -> DataUsageInfo {
|
|
let mut info = DataUsageInfo {
|
|
last_update: Some(last_update),
|
|
..Default::default()
|
|
};
|
|
info.buckets_usage.insert(
|
|
bucket.to_string(),
|
|
BucketUsageInfo {
|
|
objects_count: 3,
|
|
size: 42,
|
|
..Default::default()
|
|
},
|
|
);
|
|
info.bucket_sizes.insert(bucket.to_string(), 42);
|
|
info.buckets_count = 1;
|
|
info.calculate_totals();
|
|
info
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn data_usage_snapshot_recovery_wires_primary_backup_and_negative_cache() {
|
|
let temp_root = tempfile::tempdir().expect("create test temp root");
|
|
let env = TestECStoreEnv::builder()
|
|
.base_dir(temp_root.path())
|
|
.init_bucket_metadata(false)
|
|
.build()
|
|
.await;
|
|
let primary = format!("{BUCKET_META_PREFIX}/{DATA_USAGE_OBJECT_NAME}");
|
|
let backup = format!("{primary}.bkp");
|
|
let last_update = SystemTime::UNIX_EPOCH + Duration::from_secs(100);
|
|
let expected = snapshot("bucket-a", last_update);
|
|
|
|
store_data_usage_in_backend(expected.clone(), env.ecstore.clone())
|
|
.await
|
|
.expect("seed primary snapshot");
|
|
let valid = read_config(env.ecstore.clone(), &primary).await.expect("read seeded primary");
|
|
save_config(env.ecstore.clone(), &backup, valid.clone())
|
|
.await
|
|
.expect("seed backup snapshot");
|
|
|
|
let corrupt = b"{not json".to_vec();
|
|
save_config(env.ecstore.clone(), &primary, corrupt.clone())
|
|
.await
|
|
.expect("corrupt primary snapshot");
|
|
let recovered = load_data_usage_from_backend(env.ecstore.clone())
|
|
.await
|
|
.expect("public loader must fall back to backup");
|
|
assert!(recovered.buckets_usage.contains_key("bucket-a"));
|
|
|
|
store_data_usage_in_backend(expected, env.ecstore.clone())
|
|
.await
|
|
.expect("equal backup timestamp must repair primary");
|
|
let repaired = read_config(env.ecstore.clone(), &primary)
|
|
.await
|
|
.expect("read repaired primary");
|
|
serde_json::from_slice::<DataUsageInfo>(&repaired).expect("primary must contain a valid snapshot after repair");
|
|
|
|
save_config(env.ecstore.clone(), &primary, corrupt.clone())
|
|
.await
|
|
.expect("corrupt primary before destructive update");
|
|
remove_bucket_usage_from_backend(env.ecstore.clone(), "bucket-a")
|
|
.await
|
|
.expect_err("destructive updates must not promote a backup snapshot");
|
|
assert_eq!(
|
|
read_config(env.ecstore.clone(), &primary)
|
|
.await
|
|
.expect("read corrupt primary"),
|
|
corrupt
|
|
);
|
|
|
|
delete_config(env.ecstore.clone(), &backup)
|
|
.await
|
|
.expect("remove backup before empty-primary check");
|
|
save_config(env.ecstore.clone(), &primary, Vec::new())
|
|
.await
|
|
.expect("write empty primary snapshot");
|
|
load_data_usage_from_backend(env.ecstore.clone())
|
|
.await
|
|
.expect_err("an empty primary with no backup must not look missing");
|
|
|
|
delete_config(env.ecstore.clone(), &primary)
|
|
.await
|
|
.expect("remove primary before empty-backup check");
|
|
save_config(env.ecstore.clone(), &backup, Vec::new())
|
|
.await
|
|
.expect("write empty backup snapshot");
|
|
load_data_usage_from_backend(env.ecstore.clone())
|
|
.await
|
|
.expect_err("an empty backup must not look missing");
|
|
|
|
delete_config(env.ecstore.clone(), &backup)
|
|
.await
|
|
.expect("remove backup before negative-cache check");
|
|
save_config(env.ecstore.clone(), &primary, corrupt.clone())
|
|
.await
|
|
.expect("restore corrupt primary before negative-cache check");
|
|
load_data_usage_from_backend_cached(env.ecstore.clone())
|
|
.await
|
|
.expect_err("corrupt primary without backup must fail");
|
|
save_config(env.ecstore.clone(), &primary, valid)
|
|
.await
|
|
.expect("repair primary without invalidating the in-process cache");
|
|
load_data_usage_from_backend(env.ecstore.clone())
|
|
.await
|
|
.expect("direct load must observe repaired primary");
|
|
load_data_usage_from_backend_cached(env.ecstore.clone())
|
|
.await
|
|
.expect_err("cached loader must reuse the recent failure until its TTL expires");
|
|
}
|