test(ilm): activate ignored ILM integration tests via serial CI lane (#4682)

Implements ilm-1 from the ILM test-strategy plan: the 33 #[ignore]d ILM
integration tests (14 in crates/scanner/tests/lifecycle_integration_test.rs,
19 in rustfs/src/app/lifecycle_transition_api_test.rs) never ran anywhere.
This adds a dedicated serialized CI job and repairs the app-layer suite,
activating 29 of them.

- ci.yml: new test-ilm-integration-serial job running the two ILM test
  surfaces with 'cargo nextest run -j1 --run-ignored ignored-only'. The
  tests share process-global singletons and fixed ports (9002/9003);
  serial_test's #[serial] does not serialize across nextest's
  process-per-test boundary, so -j1 is the serialization mechanism.
- app suite repair: the 19 app tests rotted after the InstanceContext
  migration (usecases resolve the store via AppContext; the test env never
  installed one, so every store-touching call failed with InternalError
  'Not init'). Add a test-only install_test_app_context helper behind the
  sanctioned context/runtime_sources boundaries and switch the tests from
  without_context() to from_global(). All 19 now pass.
- delete test_lifecycle_transition_basic (+3 orphaned helpers): required
  an external MinIO at localhost:9000 and slept 1200s; superseded by the
  mock-tier tests in the same file.
- fix a timing flake: poll free-version metadata removal instead of
  asserting a single read right after remote-object absence.
- 3 scanner tests fail on main for product reasons (multipart restore
  UnexpectedEof; noncurrent transition/expiry after immediate compensation
  transition on versioned buckets); they keep #[ignore] with a backlog
  reference and are excluded from the lane by name.

Lane: 29 tests, ~43s test time, green twice locally.

Refs rustfs/backlog#1148 (ilm-1), rustfs/backlog#1155.
This commit is contained in:
Zhengchao An
2026-07-10 21:01:37 +08:00
committed by GitHub
parent 12c44abce0
commit 515e14cd42
6 changed files with 140 additions and 225 deletions
+39
View File
@@ -183,6 +183,45 @@ jobs:
- name: Run rebalance/decommission migration proofs
run: ./scripts/check_migration_gate_count.sh
# Dedicated serial lane for the ILM / lifecycle integration tests. These tests
# drive the object layer through process-global singletons (the GLOBAL_ENV
# ECStore, the global tier-config manager, background-expiry workers) and bind
# fixed ports (9002 for the scanner suite, 9003 for the app suite), so they are
# marked #[ignore] to keep them out of the default parallel `cargo nextest run
# --all` pass. Run them here explicitly with `--run-ignored ignored-only` and
# `-j1` so no two of them share global state or race for a port at once.
# serial_test's #[serial] does NOT serialize across nextest's process-per-test
# boundary (see .config/nextest.toml); `-j1` is what actually serializes them.
# See rustfs/backlog#1148 (ilm-1) and #1155.
test-ilm-integration-serial:
name: ILM Integration (serial)
if: github.event_name != 'pull_request' || github.event.action != 'closed'
runs-on: sm-standard-4
timeout-minutes: 45
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
steps:
- name: Checkout repository
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
- name: Setup Rust environment
uses: ./.github/actions/setup
with:
rust-version: stable
cache-shared-key: ci-ilm-serial
github-token: ${{ secrets.GITHUB_TOKEN }}
cache-save-if: ${{ github.ref == 'refs/heads/main' }}
# Three scanner tests fail on main independently of this lane (restore of
# a transitioned multipart object, noncurrent transition/expiry after an
# immediate compensation transition); they keep #[ignore] with a backlog
# reference and are excluded here by name until fixed (rustfs/backlog#1148).
- name: Run ignored ILM integration tests serially
run: |
cargo nextest run -j1 --run-ignored ignored-only \
-p rustfs-scanner -p rustfs \
-E '(binary(lifecycle_integration_test) or (package(rustfs) and test(lifecycle_transition_api_test))) and not (test(test_transition_and_restore_flows) or test(test_noncurrent_expiry_still_works_after_immediate_compensation_transition) or test(test_noncurrent_transition_still_works_after_immediate_compensation_transition))'
test-and-lint-rio-v2:
name: Test and Lint (rio-v2)
if: github.event_name != 'pull_request' || github.event.action != 'closed'
@@ -320,11 +320,6 @@ async fn set_bucket_lifecycle_delmarker_expiration(bucket_name: &str, days: i64)
Ok(())
}
#[allow(dead_code)]
async fn set_bucket_lifecycle_transition(bucket_name: &str) -> Result<(), Box<dyn std::error::Error>> {
set_bucket_lifecycle_transition_with_tier(bucket_name, "COLDTIER44").await
}
#[allow(dead_code)]
async fn set_bucket_lifecycle_transition_with_tier(
bucket_name: &str,
@@ -364,67 +359,6 @@ async fn set_bucket_lifecycle_transition_with_tier(
Ok(())
}
/// Test helper: Create a test tier
#[allow(dead_code)]
async fn create_test_tier(server: u32) {
let args = TierConfig {
version: "v1".to_string(),
tier_type: TierType::MinIO,
name: "COLDTIER44".to_string(),
s3: None,
aliyun: None,
tencent: None,
huaweicloud: None,
azure: None,
gcs: None,
r2: None,
rustfs: None,
minio: if server == 1 {
Some(TierMinIO {
access_key: "minioadmin".to_string(),
secret_key: "minioadmin".to_string(),
bucket: "hello".to_string(),
endpoint: "http://127.0.0.1:9000".to_string(),
prefix: format!("mypre{}/", uuid::Uuid::new_v4()),
region: "".to_string(),
..Default::default()
})
} else if server == 2 {
let test_compatible_server = std::env::var("TEST_MINIO_SERVER").unwrap_or_else(|_| "localhost:9000".to_string());
Some(TierMinIO {
access_key: "minioadmin".to_string(),
secret_key: "minioadmin".to_string(),
bucket: "mblock2".to_string(),
endpoint: format!("http://{}", test_compatible_server),
prefix: format!("mypre{}/", uuid::Uuid::new_v4()),
region: "".to_string(),
..Default::default()
})
} else {
Some(TierMinIO {
access_key: "minioadmin".to_string(),
secret_key: "minioadmin".to_string(),
bucket: "mblock2".to_string(),
endpoint: "http://127.0.0.1:9020".to_string(),
prefix: format!("mypre{}/", uuid::Uuid::new_v4()),
region: "".to_string(),
..Default::default()
})
},
};
let tier_config_mgr_handle = get_global_tier_config_mgr();
let mut tier_config_mgr = tier_config_mgr_handle.write().await;
if let Err(err) = tier_config_mgr.add(args, false).await {
println!("tier_config_mgr add failed, e: {err:?}");
panic!("tier add failed. {err}");
}
if let Err(e) = tier_config_mgr.save().await {
println!("tier_config_mgr save failed, e: {e:?}");
panic!("tier save failed");
}
println!("Created test tier: COLDTIER44");
}
/// Test helper: Check if object exists
async fn object_exists(ecstore: &Arc<ECStore>, bucket: &str, object: &str) -> bool {
match (**ecstore).get_object_info(bucket, object, &ObjectOptions::default()).await {
@@ -445,18 +379,6 @@ async fn object_is_delete_marker(ecstore: &Arc<ECStore>, bucket: &str, object: &
}
}
/// Test helper: Check if object exists
#[allow(dead_code)]
async fn object_is_transitioned(ecstore: &Arc<ECStore>, bucket: &str, object: &str) -> bool {
if let Ok(oi) = (**ecstore).get_object_info(bucket, object, &ObjectOptions::default()).await {
println!("oi: {oi:?}");
!oi.transitioned_object.status.is_empty()
} else {
println!("object_is_transitioned is error");
panic!("object_is_transitioned is error");
}
}
#[allow(dead_code)]
async fn wait_for_object_absence(ecstore: &Arc<ECStore>, bucket: &str, object: &str, timeout: Duration) -> bool {
let deadline = tokio::time::Instant::now() + timeout;
@@ -517,6 +439,24 @@ async fn free_version_count(disk_path: &Path, bucket: &str, object: &str) -> usi
.len()
}
/// The free-version metadata removal lands asynchronously after the remote
/// object disappears, so poll instead of asserting a single read.
async fn wait_for_free_version_absence(disk_path: &Path, bucket: &str, object: &str, timeout: Duration) -> bool {
let deadline = tokio::time::Instant::now() + timeout;
loop {
if free_version_count(disk_path, bucket, object).await == 0 {
return true;
}
if tokio::time::Instant::now() >= deadline {
return false;
}
tokio::time::sleep(Duration::from_millis(50)).await;
}
}
async fn object_version_count(ecstore: &Arc<ECStore>, bucket: &str, object: &str) -> usize {
let mut marker = None;
let mut version_marker = None;
@@ -829,98 +769,7 @@ mod serial_tests {
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
#[serial]
#[ignore]
async fn test_lifecycle_transition_basic() {
let (_disk_paths, ecstore) = setup_test_env().await;
create_test_tier(2).await;
// Create test bucket and object
let suffix = uuid::Uuid::new_v4().simple().to_string();
let bucket_name = format!("test-lc-transition-{}", &suffix[..8]);
let object_name = "test/object.txt"; // Match the lifecycle rule prefix "test/"
let test_data = b"Hello, this is test data for lifecycle expiry!";
create_test_lock_bucket(&ecstore, bucket_name.as_str()).await;
upload_test_object(
&ecstore,
bucket_name.as_str(),
object_name,
b"Hello, this is test data for lifecycle expiry 1111-11111111-1111 !",
)
.await;
//create_test_bucket(&ecstore, bucket_name.as_str()).await;
upload_test_object(&ecstore, bucket_name.as_str(), object_name, test_data).await;
// Verify object exists initially
assert!(object_exists(&ecstore, bucket_name.as_str(), object_name).await);
println!("✅ Object exists before lifecycle processing");
// Set lifecycle configuration with very short expiry (0 days = immediate expiry)
set_bucket_lifecycle_transition(bucket_name.as_str())
.await
.expect("Failed to set lifecycle configuration");
println!("✅ Lifecycle configuration set for bucket: {bucket_name}");
// Verify lifecycle configuration was set
match get_bucket_metadata(bucket_name.as_str()).await {
Ok(bucket_meta) => {
assert!(bucket_meta.lifecycle_config.is_some());
println!("✅ Bucket metadata retrieved successfully");
}
Err(e) => {
println!("❌ Error retrieving bucket metadata: {e:?}");
}
}
let ctx = CancellationToken::new();
// Start scanner
init_data_scanner(ctx.clone(), ecstore.clone()).await;
println!("✅ Scanner started");
// Wait for scanner to process lifecycle rules
tokio::time::sleep(Duration::from_secs(1200)).await;
// Check if object has been expired (deleted)
let check_result = object_is_transitioned(&ecstore, &bucket_name, object_name).await;
println!("Object exists after lifecycle processing: {check_result}");
if check_result {
println!("✅ Object was transitioned by lifecycle processing");
// Let's try to get object info to see its details
match ecstore
.get_object_info(bucket_name.as_str(), object_name, &ObjectOptions::default())
.await
{
Ok(obj_info) => {
println!(
"Object info: name={}, size={}, mod_time={:?}",
obj_info.name, obj_info.size, obj_info.mod_time
);
println!("Object info: transitioned_object={:?}", obj_info.transitioned_object);
}
Err(e) => {
println!("Error getting object info: {e:?}");
}
}
} else {
println!("❌ Object was not transitioned by lifecycle processing");
}
assert!(check_result);
println!("✅ Object successfully transitioned");
// Stop scanner
ctx.cancel();
println!("✅ Scanner stopped");
println!("Lifecycle transition basic test completed");
}
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
#[serial]
#[ignore = "requires isolated global object layer state"]
#[ignore = "FAILING on main: excluded from the serial ILM lane pending a fix, see rustfs/backlog#1148 (ilm-1 partial)"]
async fn test_transition_and_restore_flows() {
let (_disk_paths, ecstore) = setup_test_env().await;
@@ -1229,7 +1078,7 @@ mod serial_tests {
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
#[serial]
#[ignore = "requires isolated global object layer state"]
#[ignore = "global-state ILM integration test: runs serialized in the CI ILM Integration (serial) lane, see ci.yml test-ilm-integration-serial and rustfs/backlog#1148 (ilm-1)"]
async fn test_scanner_enqueues_free_version_cleanup_for_stale_transitioned_object() {
let (disk_paths, ecstore) = setup_isolated_test_env(false).await;
@@ -1276,9 +1125,8 @@ mod serial_tests {
wait_for_remote_absence(&backend, &stale_remote_object, TRANSITION_WAIT_TIMEOUT).await,
"scanner should enqueue stale free-version cleanup for the transitioned remote object"
);
assert_eq!(
free_version_count(&disk_paths[0], bucket_name.as_str(), object_name).await,
0,
assert!(
wait_for_free_version_absence(&disk_paths[0], bucket_name.as_str(), object_name, TRANSITION_WAIT_TIMEOUT).await,
"free-version metadata should be removed after scanner-triggered cleanup"
);
assert!(
@@ -1289,7 +1137,7 @@ mod serial_tests {
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
#[serial]
#[ignore = "requires isolated global object layer state"]
#[ignore = "global-state ILM integration test: runs serialized in the CI ILM Integration (serial) lane, see ci.yml test-ilm-integration-serial and rustfs/backlog#1148 (ilm-1)"]
async fn test_scanner_cleanup_still_works_after_immediate_compensation_transition() {
let (disk_paths, ecstore) = setup_isolated_test_env(false).await;
@@ -1337,16 +1185,15 @@ mod serial_tests {
wait_for_remote_absence(&backend, &stale_remote_object, TRANSITION_WAIT_TIMEOUT).await,
"scanner should clean stale remote object even after immediate compensation transitioned it"
);
assert_eq!(
free_version_count(&disk_paths[0], bucket_name.as_str(), object_name).await,
0,
assert!(
wait_for_free_version_absence(&disk_paths[0], bucket_name.as_str(), object_name, TRANSITION_WAIT_TIMEOUT).await,
"free-version metadata should be removed after scanner cleanup"
);
}
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
#[serial]
#[ignore = "requires isolated global object layer state"]
#[ignore = "global-state ILM integration test: runs serialized in the CI ILM Integration (serial) lane, see ci.yml test-ilm-integration-serial and rustfs/backlog#1148 (ilm-1)"]
async fn test_existing_object_backfill_is_idempotent_after_immediate_compensation_transition() {
let (_disk_paths, ecstore) = setup_isolated_test_env(false).await;
@@ -1389,7 +1236,7 @@ mod serial_tests {
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
#[serial]
#[ignore = "requires isolated global object layer state"]
#[ignore = "FAILING on main: excluded from the serial ILM lane pending a fix, see rustfs/backlog#1148 (ilm-1 partial)"]
async fn test_noncurrent_expiry_still_works_after_immediate_compensation_transition() {
let (disk_paths, ecstore) = setup_isolated_test_env(true).await;
@@ -1473,7 +1320,7 @@ mod serial_tests {
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
#[serial]
#[ignore = "requires isolated global object layer state"]
#[ignore = "FAILING on main: excluded from the serial ILM lane pending a fix, see rustfs/backlog#1148 (ilm-1 partial)"]
async fn test_noncurrent_transition_still_works_after_immediate_compensation_transition() {
let (disk_paths, ecstore) = setup_isolated_test_env(true).await;
@@ -1556,7 +1403,7 @@ mod serial_tests {
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
#[serial]
#[ignore = "requires isolated global object layer state"]
#[ignore = "global-state ILM integration test: runs serialized in the CI ILM Integration (serial) lane, see ci.yml test-ilm-integration-serial and rustfs/backlog#1148 (ilm-1)"]
async fn test_modeled_versioned_delete_creates_delete_marker_after_immediate_compensation_transition() {
let (_disk_paths, ecstore) = setup_isolated_test_env(true).await;
@@ -1604,7 +1451,7 @@ mod serial_tests {
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
#[serial]
#[ignore = "requires isolated global object layer state"]
#[ignore = "global-state ILM integration test: runs serialized in the CI ILM Integration (serial) lane, see ci.yml test-ilm-integration-serial and rustfs/backlog#1148 (ilm-1)"]
async fn test_modeled_delete_marker_cleanup_after_immediate_compensation_transition() {
let (disk_paths, ecstore) = setup_isolated_test_env(true).await;
@@ -1681,7 +1528,7 @@ mod serial_tests {
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
#[serial]
#[ignore = "requires isolated global object layer state"]
#[ignore = "global-state ILM integration test: runs serialized in the CI ILM Integration (serial) lane, see ci.yml test-ilm-integration-serial and rustfs/backlog#1148 (ilm-1)"]
async fn test_scanner_expires_zero_day_current_version() {
let (disk_paths, ecstore) = setup_isolated_test_env(false).await;
@@ -1708,7 +1555,7 @@ mod serial_tests {
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
#[serial]
#[ignore = "requires isolated global object layer state"]
#[ignore = "global-state ILM integration test: runs serialized in the CI ILM Integration (serial) lane, see ci.yml test-ilm-integration-serial and rustfs/backlog#1148 (ilm-1)"]
async fn test_put_object_immediately_enqueues_zero_day_current_expiry() {
let (_disk_paths, ecstore) = setup_isolated_test_env(true).await;
@@ -1746,7 +1593,7 @@ mod serial_tests {
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
#[serial]
#[ignore = "requires isolated global object layer state"]
#[ignore = "global-state ILM integration test: runs serialized in the CI ILM Integration (serial) lane, see ci.yml test-ilm-integration-serial and rustfs/backlog#1148 (ilm-1)"]
async fn test_scanner_expires_zero_day_noncurrent_version() {
let (disk_paths, ecstore) = setup_isolated_test_env(false).await;
@@ -1813,7 +1660,7 @@ mod serial_tests {
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
#[serial]
#[ignore = "requires isolated global object layer state"]
#[ignore = "global-state ILM integration test: runs serialized in the CI ILM Integration (serial) lane, see ci.yml test-ilm-integration-serial and rustfs/backlog#1148 (ilm-1)"]
async fn test_put_object_immediately_enqueues_zero_day_noncurrent_expiry() {
let (_disk_paths, ecstore) = setup_isolated_test_env(true).await;
@@ -1898,7 +1745,7 @@ mod serial_tests {
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
#[serial]
#[ignore = "requires isolated global object layer state"]
#[ignore = "global-state ILM integration test: runs serialized in the CI ILM Integration (serial) lane, see ci.yml test-ilm-integration-serial and rustfs/backlog#1148 (ilm-1)"]
async fn test_background_scanner_expires_zero_day_current_version_for_exact_key_prefix() {
let (_disk_paths, ecstore) = setup_isolated_test_env(true).await;
+14
View File
@@ -369,3 +369,17 @@ pub fn publish_global_app_context(context: Arc<AppContext>) -> Arc<AppContext> {
pub fn get_global_app_context() -> Option<Arc<AppContext>> {
APP_CONTEXT_SINGLETON.get().cloned()
}
/// Test-only: initialize IAM around the given store, build a
/// default-interface context, and publish it as the process-global context so
/// usecases constructed via `from_global()` resolve this store
/// (rustfs/backlog#1148 ilm-1).
#[cfg(test)]
pub async fn install_test_app_context(object_store: Arc<ECStore>) {
let iam = rustfs_iam::init_iam_sys(object_store.clone())
.await
.expect("init IAM system for test AppContext");
let kms = Arc::new(KmsServiceManager::new());
let context = AppContext::with_default_interfaces(object_store, iam, kms);
publish_global_app_context(Arc::new(context));
}
+43 -37
View File
@@ -137,6 +137,12 @@ async fn setup_test_env() -> (Vec<PathBuf>, Arc<ECStore>) {
lifecycle::bucket_lifecycle_ops::init_background_expiry(ecstore.clone()).await;
// The usecases resolve the object store through the ambient AppContext
// since the backlog#1052 InstanceContext migration; without one installed
// every store-touching usecase call fails with InternalError "Not init".
// Publish a default-interface context wrapping this test store.
crate::app::runtime_sources::install_test_app_context(ecstore.clone()).await;
let _ = GLOBAL_ENV.set((disk_paths.clone(), ecstore.clone()));
(disk_paths, ecstore)
@@ -538,11 +544,11 @@ async fn live_object_version_count(ecstore: &Arc<ECStore>, bucket: &str, object:
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
#[serial]
#[ignore = "requires isolated global object layer state"]
#[ignore = "global-state ILM integration test: runs serialized in the CI ILM Integration (serial) lane, see ci.yml test-ilm-integration-serial and rustfs/backlog#1148 (ilm-1)"]
async fn put_object_if_none_match_existing_object_returns_precondition_failed() {
let (_disk_paths, ecstore) = setup_test_env().await;
let fs = FS::new();
let usecase = DefaultObjectUsecase::without_context();
let usecase = DefaultObjectUsecase::from_global();
let bucket = format!("test-put-if-none-match-{}", &Uuid::new_v4().simple().to_string()[..8]);
let object = "test/object.txt";
@@ -591,10 +597,10 @@ async fn put_object_if_none_match_existing_object_returns_precondition_failed()
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
#[serial]
#[ignore = "requires isolated global object layer state"]
#[ignore = "global-state ILM integration test: runs serialized in the CI ILM Integration (serial) lane, see ci.yml test-ilm-integration-serial and rustfs/backlog#1148 (ilm-1)"]
async fn copy_object_if_none_match_existing_destination_returns_precondition_failed() {
let (_disk_paths, ecstore) = setup_test_env().await;
let usecase = DefaultObjectUsecase::without_context();
let usecase = DefaultObjectUsecase::from_global();
let src_bucket = format!("test-copy-if-none-match-src-{}", &Uuid::new_v4().simple().to_string()[..8]);
let dst_bucket = format!("test-copy-if-none-match-dst-{}", &Uuid::new_v4().simple().to_string()[..8]);
@@ -640,11 +646,11 @@ async fn copy_object_if_none_match_existing_destination_returns_precondition_fai
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
#[serial]
#[ignore = "requires isolated global object layer state"]
#[ignore = "global-state ILM integration test: runs serialized in the CI ILM Integration (serial) lane, see ci.yml test-ilm-integration-serial and rustfs/backlog#1148 (ilm-1)"]
async fn copy_object_allows_new_version_for_locked_destination_but_blocks_explicit_overwrite() {
let (_disk_paths, ecstore) = setup_test_env().await;
let fs = FS::new();
let usecase = DefaultObjectUsecase::without_context();
let usecase = DefaultObjectUsecase::from_global();
let bucket = format!("test-copy-object-lock-{}", &Uuid::new_v4().simple().to_string()[..8]);
let src_object = "test/source.txt";
@@ -728,7 +734,7 @@ async fn copy_object_allows_new_version_for_locked_destination_but_blocks_explic
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
#[serial]
#[ignore = "requires isolated global object layer state"]
#[ignore = "global-state ILM integration test: runs serialized in the CI ILM Integration (serial) lane, see ci.yml test-ilm-integration-serial and rustfs/backlog#1148 (ilm-1)"]
async fn concurrent_reverse_copy_object_does_not_deadlock_with_reader_locks() {
temp_env::async_with_vars([(ENV_OBJECT_LOCK_OPTIMIZATION_ENABLE, Some("false"))], async {
let (_disk_paths, ecstore) = setup_test_env().await;
@@ -768,13 +774,13 @@ async fn concurrent_reverse_copy_object_does_not_deadlock_with_reader_locks() {
let start_a = start.clone();
let a_to_b = tokio::spawn(async move {
start_a.wait().await;
let usecase = DefaultObjectUsecase::without_context();
let usecase = DefaultObjectUsecase::from_global();
Box::pin(usecase.execute_copy_object(build_request(a_to_b_input, Method::PUT))).await
});
let start_b = start.clone();
let b_to_a = tokio::spawn(async move {
start_b.wait().await;
let usecase = DefaultObjectUsecase::without_context();
let usecase = DefaultObjectUsecase::from_global();
Box::pin(usecase.execute_copy_object(build_request(b_to_a_input, Method::PUT))).await
});
@@ -809,11 +815,11 @@ async fn concurrent_reverse_copy_object_does_not_deadlock_with_reader_locks() {
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
#[serial]
#[ignore = "requires isolated global object layer state"]
#[ignore = "global-state ILM integration test: runs serialized in the CI ILM Integration (serial) lane, see ci.yml test-ilm-integration-serial and rustfs/backlog#1148 (ilm-1)"]
async fn put_and_copy_object_transition_immediately_via_usecases() {
let (_disk_paths, ecstore) = setup_test_env().await;
let fs = FS::new();
let usecase = DefaultObjectUsecase::without_context();
let usecase = DefaultObjectUsecase::from_global();
let tier_name = format!("COLDTIER{}", &Uuid::new_v4().simple().to_string()[..8]).to_uppercase();
let backend = register_mock_tier(&tier_name).await;
@@ -886,10 +892,10 @@ async fn put_and_copy_object_transition_immediately_via_usecases() {
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
#[serial]
#[ignore = "requires isolated global object layer state"]
#[ignore = "global-state ILM integration test: runs serialized in the CI ILM Integration (serial) lane, see ci.yml test-ilm-integration-serial and rustfs/backlog#1148 (ilm-1)"]
async fn complete_multipart_upload_transitions_immediately_via_usecase() {
let (_disk_paths, ecstore) = setup_test_env().await;
let usecase = DefaultMultipartUsecase::without_context();
let usecase = DefaultMultipartUsecase::from_global();
let tier_name = format!("COLDTIER{}", &Uuid::new_v4().simple().to_string()[..8]).to_uppercase();
let backend = register_mock_tier(&tier_name).await;
@@ -943,7 +949,7 @@ async fn complete_multipart_upload_transitions_immediately_via_usecase() {
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
#[serial]
#[ignore = "requires isolated global object layer state"]
#[ignore = "global-state ILM integration test: runs serialized in the CI ILM Integration (serial) lane, see ci.yml test-ilm-integration-serial and rustfs/backlog#1148 (ilm-1)"]
async fn get_transitioned_object_uses_remote_codec_fallback_path() {
with_get_codec_streaming_remote_probe_env(|| async {
let (_disk_paths, ecstore) = setup_test_env().await;
@@ -1003,10 +1009,10 @@ async fn get_transitioned_object_uses_remote_codec_fallback_path() {
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
#[serial]
#[ignore = "requires isolated global object layer state"]
#[ignore = "global-state ILM integration test: runs serialized in the CI ILM Integration (serial) lane, see ci.yml test-ilm-integration-serial and rustfs/backlog#1148 (ilm-1)"]
async fn delete_transitioned_object_removes_remote_tier_copy_via_usecase() {
let (_disk_paths, ecstore) = setup_test_env().await;
let usecase = DefaultObjectUsecase::without_context();
let usecase = DefaultObjectUsecase::from_global();
let tier_name = format!("COLDTIER{}", &Uuid::new_v4().simple().to_string()[..8]).to_uppercase();
let backend = register_mock_tier(&tier_name).await;
@@ -1059,7 +1065,7 @@ async fn delete_transitioned_object_removes_remote_tier_copy_via_usecase() {
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
#[serial]
#[ignore = "requires isolated global object layer state"]
#[ignore = "global-state ILM integration test: runs serialized in the CI ILM Integration (serial) lane, see ci.yml test-ilm-integration-serial and rustfs/backlog#1148 (ilm-1)"]
async fn lifecycle_transition_marks_dirty_disks_for_capacity_manager() {
let (disk_paths, ecstore) = setup_test_env().await;
let manager = create_isolated_manager(HybridStrategyConfig::default());
@@ -1102,7 +1108,7 @@ async fn lifecycle_transition_marks_dirty_disks_for_capacity_manager() {
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
#[serial]
#[ignore = "requires isolated global object layer state"]
#[ignore = "global-state ILM integration test: runs serialized in the CI ILM Integration (serial) lane, see ci.yml test-ilm-integration-serial and rustfs/backlog#1148 (ilm-1)"]
async fn immediate_transition_timeout_eventually_completes_via_compensation() {
let (_disk_paths, ecstore) = setup_test_env().await;
let tier_name = format!("COLDTIER{}", &Uuid::new_v4().simple().to_string()[..8]).to_uppercase();
@@ -1133,10 +1139,10 @@ async fn immediate_transition_timeout_eventually_completes_via_compensation() {
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
#[serial]
#[ignore = "requires isolated global object layer state"]
#[ignore = "global-state ILM integration test: runs serialized in the CI ILM Integration (serial) lane, see ci.yml test-ilm-integration-serial and rustfs/backlog#1148 (ilm-1)"]
async fn compensation_driven_copy_still_completes_transition() {
let (_disk_paths, ecstore) = setup_test_env().await;
let usecase = DefaultObjectUsecase::without_context();
let usecase = DefaultObjectUsecase::from_global();
let tier_name = format!("COLDTIER{}", &Uuid::new_v4().simple().to_string()[..8]).to_uppercase();
let backend = register_mock_tier(&tier_name).await;
@@ -1183,10 +1189,10 @@ async fn compensation_driven_copy_still_completes_transition() {
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
#[serial]
#[ignore = "requires isolated global object layer state"]
#[ignore = "global-state ILM integration test: runs serialized in the CI ILM Integration (serial) lane, see ci.yml test-ilm-integration-serial and rustfs/backlog#1148 (ilm-1)"]
async fn compensation_driven_complete_multipart_upload_still_transitions() {
let (_disk_paths, ecstore) = setup_test_env().await;
let usecase = DefaultMultipartUsecase::without_context();
let usecase = DefaultMultipartUsecase::from_global();
let tier_name = format!("COLDTIER{}", &Uuid::new_v4().simple().to_string()[..8]).to_uppercase();
let backend = register_mock_tier(&tier_name).await;
@@ -1243,10 +1249,10 @@ async fn compensation_driven_complete_multipart_upload_still_transitions() {
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
#[serial]
#[ignore = "requires isolated global object layer state"]
#[ignore = "global-state ILM integration test: runs serialized in the CI ILM Integration (serial) lane, see ci.yml test-ilm-integration-serial and rustfs/backlog#1148 (ilm-1)"]
async fn compensation_driven_transition_still_cleans_remote_tier_on_delete() {
let (_disk_paths, ecstore) = setup_test_env().await;
let usecase = DefaultObjectUsecase::without_context();
let usecase = DefaultObjectUsecase::from_global();
let tier_name = format!("COLDTIER{}", &Uuid::new_v4().simple().to_string()[..8]).to_uppercase();
let backend = register_mock_tier(&tier_name).await;
@@ -1299,10 +1305,10 @@ async fn compensation_driven_transition_still_cleans_remote_tier_on_delete() {
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
#[serial]
#[ignore = "requires isolated global object layer state"]
#[ignore = "global-state ILM integration test: runs serialized in the CI ILM Integration (serial) lane, see ci.yml test-ilm-integration-serial and rustfs/backlog#1148 (ilm-1)"]
async fn compensation_driven_versioned_delete_still_creates_delete_marker() {
let (_disk_paths, ecstore) = setup_test_env().await;
let usecase = DefaultObjectUsecase::without_context();
let usecase = DefaultObjectUsecase::from_global();
let tier_name = format!("COLDTIER{}", &Uuid::new_v4().simple().to_string()[..8]).to_uppercase();
let backend = register_mock_tier(&tier_name).await;
@@ -1353,11 +1359,11 @@ async fn compensation_driven_versioned_delete_still_creates_delete_marker() {
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
#[serial]
#[ignore = "requires isolated global object layer state"]
#[ignore = "global-state ILM integration test: runs serialized in the CI ILM Integration (serial) lane, see ci.yml test-ilm-integration-serial and rustfs/backlog#1148 (ilm-1)"]
async fn compensation_driven_delete_marker_still_honors_lifecycle_cleanup() {
let (_disk_paths, ecstore) = setup_test_env().await;
let usecase = DefaultObjectUsecase::without_context();
let bucket_usecase = DefaultBucketUsecase::without_context();
let usecase = DefaultObjectUsecase::from_global();
let bucket_usecase = DefaultBucketUsecase::from_global();
let tier_name = format!("COLDTIER{}", &Uuid::new_v4().simple().to_string()[..8]).to_uppercase();
let backend = register_mock_tier(&tier_name).await;
@@ -1430,10 +1436,10 @@ async fn compensation_driven_delete_marker_still_honors_lifecycle_cleanup() {
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
#[serial]
#[ignore = "requires isolated global object layer state"]
#[ignore = "global-state ILM integration test: runs serialized in the CI ILM Integration (serial) lane, see ci.yml test-ilm-integration-serial and rustfs/backlog#1148 (ilm-1)"]
async fn put_bucket_lifecycle_configuration_expires_existing_objects() {
let (_disk_paths, ecstore) = setup_test_env().await;
let usecase = DefaultBucketUsecase::without_context();
let usecase = DefaultBucketUsecase::from_global();
let bucket = format!("test-api-expire-existing-{}", &Uuid::new_v4().simple().to_string()[..8]);
let object = "test/existing.txt";
@@ -1464,10 +1470,10 @@ async fn put_bucket_lifecycle_configuration_expires_existing_objects() {
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
#[serial]
#[ignore = "requires isolated global object layer state"]
#[ignore = "global-state ILM integration test: runs serialized in the CI ILM Integration (serial) lane, see ci.yml test-ilm-integration-serial and rustfs/backlog#1148 (ilm-1)"]
async fn put_bucket_lifecycle_configuration_allows_expired_delete_marker_on_object_lock_bucket() {
let (_disk_paths, ecstore) = setup_test_env().await;
let usecase = DefaultBucketUsecase::without_context();
let usecase = DefaultBucketUsecase::from_global();
let bucket = format!("test-lock-expired-del-marker-{}", &Uuid::new_v4().simple().to_string()[..8]);
create_test_bucket(&ecstore, bucket.as_str()).await;
@@ -1492,10 +1498,10 @@ async fn put_bucket_lifecycle_configuration_allows_expired_delete_marker_on_obje
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
#[serial]
#[ignore = "requires isolated global object layer state"]
#[ignore = "global-state ILM integration test: runs serialized in the CI ILM Integration (serial) lane, see ci.yml test-ilm-integration-serial and rustfs/backlog#1148 (ilm-1)"]
async fn put_bucket_lifecycle_configuration_rejects_del_marker_expiration_on_object_lock_bucket() {
let (_disk_paths, ecstore) = setup_test_env().await;
let usecase = DefaultBucketUsecase::without_context();
let usecase = DefaultBucketUsecase::from_global();
let bucket = format!("test-lock-del-marker-exp-{}", &Uuid::new_v4().simple().to_string()[..8]);
create_test_bucket(&ecstore, bucket.as_str()).await;
@@ -1528,10 +1534,10 @@ async fn put_bucket_lifecycle_configuration_rejects_del_marker_expiration_on_obj
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
#[serial]
#[ignore = "requires isolated global object layer state"]
#[ignore = "global-state ILM integration test: runs serialized in the CI ILM Integration (serial) lane, see ci.yml test-ilm-integration-serial and rustfs/backlog#1148 (ilm-1)"]
async fn put_bucket_lifecycle_configuration_rejects_zero_day_del_marker_expiration_on_object_lock_bucket() {
let (_disk_paths, ecstore) = setup_test_env().await;
let usecase = DefaultBucketUsecase::without_context();
let usecase = DefaultBucketUsecase::from_global();
let bucket = format!("test-lock-del-marker-exp0-{}", &Uuid::new_v4().simple().to_string()[..8]);
create_test_bucket(&ecstore, bucket.as_str()).await;
+6
View File
@@ -63,3 +63,9 @@ pub(crate) fn current_expiry_state_handle() -> Arc<RwLock<ExpiryState>> {
pub(crate) fn current_tier_config_handle() -> Arc<RwLock<TierConfigMgr>> {
root_runtime_sources::current_tier_config_handle().unwrap_or_else(TierConfigMgr::new)
}
/// Test-only: publish a default-interface `AppContext` around the given store
/// so usecases constructed via `from_global()` resolve this store
/// (rustfs/backlog#1148 ilm-1).
#[cfg(test)]
pub(crate) use crate::runtime_sources::install_test_app_context;
+3
View File
@@ -55,6 +55,9 @@ pub(crate) fn set_test_outbound_tls_generation(generation: u64) {
context::set_test_outbound_tls_generation(generation);
}
#[cfg(test)]
pub(crate) use context::install_test_app_context;
pub(crate) fn current_app_context() -> Option<Arc<AppContext>> {
context::get_global_app_context()
}