mirror of
https://github.com/rustfs/rustfs.git
synced 2026-08-20 11:32:19 +00:00
fix(iam): align OIDC parent IDs with MinIO (#5290)
* fix(iam): align OIDC parent IDs with MinIO * test(iam): cover OIDC STS binding policy lookup * test(admin): use runtime facade for AppContext setup * test(ilm): wait for lifecycle backfill before manual failure * test(ilm): make overlapping admission check concurrent
This commit is contained in:
@@ -382,6 +382,7 @@ struct ManualTransitionRunReport {
|
||||
skipped_delete_marker: u64,
|
||||
skipped_directory: u64,
|
||||
skipped_replication: u64,
|
||||
skipped_already_transitioned: u64,
|
||||
skipped_already_in_flight: u64,
|
||||
skipped_queue_full: u64,
|
||||
skipped_queue_closed: u64,
|
||||
@@ -411,6 +412,23 @@ struct ManualTransitionQueueSnapshot {
|
||||
compensation_running: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct ScannerStatusResponse {
|
||||
metrics: ScannerStatusMetrics,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct ScannerStatusMetrics {
|
||||
lifecycle_transition: ScannerTransitionQueueState,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct ScannerTransitionQueueState {
|
||||
current_queued: u64,
|
||||
current_active: u64,
|
||||
failed: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct ManualTransitionJobStatusResponse {
|
||||
job_id: String,
|
||||
@@ -621,6 +639,41 @@ async fn wait_for_manual_transition_job_running(
|
||||
}
|
||||
}
|
||||
|
||||
async fn scanner_transition_queue_state(
|
||||
hot: &RustFSTestEnvironment,
|
||||
) -> Result<ScannerTransitionQueueState, Box<dyn std::error::Error + Send + Sync>> {
|
||||
let path = "/rustfs/admin/v3/scanner/status";
|
||||
let (status, body) = signed_admin_request(&hot.url, Method::GET, path, None, &hot.access_key, &hot.secret_key).await?;
|
||||
if !status.is_success() {
|
||||
return Err(format!("scanner status failed: status={status}, body={body}").into());
|
||||
}
|
||||
Ok(serde_json::from_str::<ScannerStatusResponse>(&body)?
|
||||
.metrics
|
||||
.lifecycle_transition)
|
||||
}
|
||||
|
||||
async fn wait_for_transition_failure_and_idle(
|
||||
hot: &RustFSTestEnvironment,
|
||||
failed_before: u64,
|
||||
deadline: StdDuration,
|
||||
) -> TestResult {
|
||||
let start = Instant::now();
|
||||
loop {
|
||||
let state = scanner_transition_queue_state(hot).await?;
|
||||
if state.failed > failed_before && state.current_queued == 0 && state.current_active == 0 {
|
||||
return Ok(());
|
||||
}
|
||||
if start.elapsed() >= deadline {
|
||||
return Err(format!(
|
||||
"transition queue did not report a new failure and become idle within {}s; failed_before={failed_before}, last={state:#?}",
|
||||
deadline.as_secs()
|
||||
)
|
||||
.into());
|
||||
}
|
||||
tokio::time::sleep(StdDuration::from_millis(100)).await;
|
||||
}
|
||||
}
|
||||
|
||||
/// Number of objects currently stored in the cold-tier bucket.
|
||||
async fn cold_tier_object_count(cold_client: &Client) -> Result<usize, Box<dyn std::error::Error + Send + Sync>> {
|
||||
let resp = cold_client.list_objects_v2().bucket(TIER_BUCKET).send().await?;
|
||||
@@ -1200,14 +1253,34 @@ async fn test_manual_transition_async_overlapping_scope_conflict_reports_active_
|
||||
.await?;
|
||||
|
||||
let before_remote_count = cold_tier_object_count(&cold_client).await?;
|
||||
let accepted = manual_transition_async_run(
|
||||
&hot,
|
||||
MANUAL_ASYNC_CONFLICT_BUCKET,
|
||||
MANUAL_ASYNC_CONFLICT_PREFIX,
|
||||
false,
|
||||
MANUAL_ASYNC_CONFLICT_OBJECTS as u64,
|
||||
)
|
||||
.await?;
|
||||
let (parent, nested) = tokio::join!(
|
||||
manual_transition_async_run_raw(
|
||||
&hot,
|
||||
MANUAL_ASYNC_CONFLICT_BUCKET,
|
||||
MANUAL_ASYNC_CONFLICT_PREFIX,
|
||||
false,
|
||||
MANUAL_ASYNC_CONFLICT_OBJECTS as u64,
|
||||
),
|
||||
manual_transition_async_run_raw(
|
||||
&hot,
|
||||
MANUAL_ASYNC_CONFLICT_BUCKET,
|
||||
MANUAL_ASYNC_CONFLICT_NESTED_PREFIX,
|
||||
false,
|
||||
MANUAL_ASYNC_CONFLICT_OBJECTS as u64,
|
||||
)
|
||||
);
|
||||
let responses = [parent?, nested?];
|
||||
let accepted = responses
|
||||
.iter()
|
||||
.find(|(status, _)| *status == reqwest::StatusCode::ACCEPTED)
|
||||
.ok_or("one concurrent overlapping-scope async run must be accepted")?;
|
||||
let conflict = responses
|
||||
.iter()
|
||||
.find(|(status, _)| *status == reqwest::StatusCode::CONFLICT)
|
||||
.ok_or("one concurrent overlapping-scope async run must report conflict")?;
|
||||
|
||||
let accepted: ManualTransitionRunResponse = serde_json::from_str(&accepted.1)?;
|
||||
let conflict: ManualTransitionJobConflictResponse = serde_json::from_str(&conflict.1)?;
|
||||
let job_id = accepted
|
||||
.job_id
|
||||
.as_deref()
|
||||
@@ -1224,22 +1297,14 @@ async fn test_manual_transition_async_overlapping_scope_conflict_reports_active_
|
||||
|
||||
assert_eq!(accepted.state, "accepted");
|
||||
assert_eq!(accepted.mode, "durable_job");
|
||||
let active = wait_for_manual_transition_job_running(&hot, status_endpoint, MANUAL_ACTIVE_CANCEL_RUNNING_TIMEOUT).await?;
|
||||
assert_eq!(active.job_id, job_id);
|
||||
let (conflict_status, conflict_body) = manual_transition_async_run_raw(
|
||||
&hot,
|
||||
MANUAL_ASYNC_CONFLICT_BUCKET,
|
||||
MANUAL_ASYNC_CONFLICT_NESTED_PREFIX,
|
||||
false,
|
||||
MANUAL_ASYNC_CONFLICT_OBJECTS as u64,
|
||||
)
|
||||
.await?;
|
||||
assert_eq!(
|
||||
conflict_status,
|
||||
reqwest::StatusCode::CONFLICT,
|
||||
"overlapping async run response: {conflict_body}"
|
||||
assert_eq!(accepted.report.bucket, MANUAL_ASYNC_CONFLICT_BUCKET);
|
||||
assert!(
|
||||
matches!(
|
||||
accepted.report.prefix.as_str(),
|
||||
MANUAL_ASYNC_CONFLICT_PREFIX | MANUAL_ASYNC_CONFLICT_NESTED_PREFIX
|
||||
),
|
||||
"overlapping async winner prefix: {accepted:#?}"
|
||||
);
|
||||
let conflict: ManualTransitionJobConflictResponse = serde_json::from_str(&conflict_body)?;
|
||||
assert_eq!(conflict.state, "conflict");
|
||||
assert_eq!(conflict.mode, "durable_job");
|
||||
assert_eq!(conflict.active_job_id, job_id);
|
||||
@@ -1252,19 +1317,20 @@ async fn test_manual_transition_async_overlapping_scope_conflict_reports_active_
|
||||
assert_eq!(terminal.status, "completed", "terminal conflict winner response: {terminal:#?}");
|
||||
assert!(!terminal.report.dry_run);
|
||||
assert_eq!(terminal.report.bucket, MANUAL_ASYNC_CONFLICT_BUCKET);
|
||||
assert_eq!(terminal.report.prefix, MANUAL_ASYNC_CONFLICT_PREFIX);
|
||||
assert_eq!(terminal.report.prefix, accepted.report.prefix);
|
||||
assert_eq!(
|
||||
terminal.report.scanned, MANUAL_ASYNC_CONFLICT_OBJECTS as u64,
|
||||
"terminal conflict winner response: {terminal:#?}"
|
||||
);
|
||||
assert_eq!(
|
||||
terminal.report.eligible, MANUAL_ASYNC_CONFLICT_OBJECTS as u64,
|
||||
terminal.report.eligible + terminal.report.skipped_already_transitioned,
|
||||
MANUAL_ASYNC_CONFLICT_OBJECTS as u64,
|
||||
"terminal conflict winner response: {terminal:#?}"
|
||||
);
|
||||
assert_eq!(terminal.report.dry_run_eligible, 0, "terminal conflict winner response: {terminal:#?}");
|
||||
assert_eq!(
|
||||
terminal.report.enqueued + terminal.report.skipped_already_in_flight,
|
||||
MANUAL_ASYNC_CONFLICT_OBJECTS as u64,
|
||||
terminal.report.eligible,
|
||||
"terminal conflict winner response: {terminal:#?}"
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -1647,6 +1713,7 @@ async fn test_manual_transition_async_worker_failure_reports_terminal_partial()
|
||||
due_mtime,
|
||||
)
|
||||
.await?;
|
||||
let transition_failures_before = scanner_transition_queue_state(&hot).await?.failed;
|
||||
put_lifecycle_transition_rule(
|
||||
&hot_client,
|
||||
MANUAL_WORKER_FAILURE_BUCKET,
|
||||
@@ -1655,6 +1722,7 @@ async fn test_manual_transition_async_worker_failure_reports_terminal_partial()
|
||||
0,
|
||||
)
|
||||
.await?;
|
||||
wait_for_transition_failure_and_idle(&hot, transition_failures_before, StdDuration::from_secs(30)).await?;
|
||||
|
||||
let accepted =
|
||||
manual_transition_async_run(&hot, MANUAL_WORKER_FAILURE_BUCKET, MANUAL_WORKER_FAILURE_PREFIX, false, 10).await?;
|
||||
|
||||
Reference in New Issue
Block a user