From 515e14cd4293314f9e73ca93b97227b4c92a9e1b Mon Sep 17 00:00:00 2001 From: Zhengchao An Date: Fri, 10 Jul 2026 21:01:37 +0800 Subject: [PATCH] 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. --- .github/workflows/ci.yml | 39 +++ .../tests/lifecycle_integration_test.rs | 223 +++--------------- rustfs/src/app/context/global.rs | 14 ++ .../src/app/lifecycle_transition_api_test.rs | 80 ++++--- rustfs/src/app/runtime_sources.rs | 6 + rustfs/src/runtime_sources.rs | 3 + 6 files changed, 140 insertions(+), 225 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6a0950e32..2145b4490 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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' diff --git a/crates/scanner/tests/lifecycle_integration_test.rs b/crates/scanner/tests/lifecycle_integration_test.rs index da5a9069d..eef3691fc 100644 --- a/crates/scanner/tests/lifecycle_integration_test.rs +++ b/crates/scanner/tests/lifecycle_integration_test.rs @@ -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> { - 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, 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, bucket: &str, object: & } } -/// Test helper: Check if object exists -#[allow(dead_code)] -async fn object_is_transitioned(ecstore: &Arc, 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, 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, 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; diff --git a/rustfs/src/app/context/global.rs b/rustfs/src/app/context/global.rs index cd47788c5..7903b4414 100644 --- a/rustfs/src/app/context/global.rs +++ b/rustfs/src/app/context/global.rs @@ -369,3 +369,17 @@ pub fn publish_global_app_context(context: Arc) -> Arc { pub fn get_global_app_context() -> Option> { 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) { + 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)); +} diff --git a/rustfs/src/app/lifecycle_transition_api_test.rs b/rustfs/src/app/lifecycle_transition_api_test.rs index 7a58346d9..097e7b40c 100644 --- a/rustfs/src/app/lifecycle_transition_api_test.rs +++ b/rustfs/src/app/lifecycle_transition_api_test.rs @@ -137,6 +137,12 @@ async fn setup_test_env() -> (Vec, Arc) { 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, 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; diff --git a/rustfs/src/app/runtime_sources.rs b/rustfs/src/app/runtime_sources.rs index 4aa70ef29..07adaf539 100644 --- a/rustfs/src/app/runtime_sources.rs +++ b/rustfs/src/app/runtime_sources.rs @@ -63,3 +63,9 @@ pub(crate) fn current_expiry_state_handle() -> Arc> { pub(crate) fn current_tier_config_handle() -> Arc> { 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; diff --git a/rustfs/src/runtime_sources.rs b/rustfs/src/runtime_sources.rs index f725e238f..9b5491cf6 100644 --- a/rustfs/src/runtime_sources.rs +++ b/rustfs/src/runtime_sources.rs @@ -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> { context::get_global_app_context() }