From 0c7b6188b2d2b68264c102ec365b3d6003d36c70 Mon Sep 17 00:00:00 2001 From: Zhengchao An Date: Wed, 9 Sep 2026 19:57:34 +0800 Subject: [PATCH] fix: reuse admin error boundary for legacy tier reconciliation (#7587) --- rustfs/src/admin/handlers/ilm_transition.rs | 72 ++++++++++++--------- 1 file changed, 42 insertions(+), 30 deletions(-) diff --git a/rustfs/src/admin/handlers/ilm_transition.rs b/rustfs/src/admin/handlers/ilm_transition.rs index 62b768cb2..2a8893a73 100644 --- a/rustfs/src/admin/handlers/ilm_transition.rs +++ b/rustfs/src/admin/handlers/ilm_transition.rs @@ -1023,33 +1023,34 @@ struct LegacyTransitionStateReconcileQuery { fn parse_legacy_transition_state_reconcile_query(query: Option<&str>) -> S3Result { let query: LegacyTransitionStateReconcileQuery = serde_urlencoded::from_bytes(query.unwrap_or_default().as_bytes()) - .map_err(|_| s3_error!(InvalidArgument, "invalid legacy transition-state reconcile query"))?; + .map_err(|_| admin_s3_error(AdminS3ErrorCode::InvalidArgument, "invalid legacy transition-state reconcile query"))?; let bucket = query .bucket .filter(|bucket| !bucket.is_empty()) - .ok_or_else(|| s3_error!(InvalidRequest, "bucket is required"))?; + .ok_or_else(|| admin_s3_error(AdminS3ErrorCode::InvalidRequest, "bucket is required"))?; if is_reserved_or_invalid_bucket(&bucket, false) { - return Err(s3_error!(InvalidBucketName, "invalid bucket name")); + return Err(admin_s3_error(AdminS3ErrorCode::InvalidBucketName, "invalid bucket name")); } let object = query .object .filter(|object| !object.is_empty()) - .ok_or_else(|| s3_error!(InvalidRequest, "object is required"))?; + .ok_or_else(|| admin_s3_error(AdminS3ErrorCode::InvalidRequest, "object is required"))?; if !is_valid_object_prefix(&object) || object.contains('\n') || object.contains('\r') { - return Err(s3_error!(InvalidArgument, "invalid object name")); + return Err(admin_s3_error(AdminS3ErrorCode::InvalidArgument, "invalid object name")); } let version_id = query .version_id .filter(|version_id| !version_id.is_empty()) - .ok_or_else(|| s3_error!(InvalidRequest, "versionId is required"))?; + .ok_or_else(|| admin_s3_error(AdminS3ErrorCode::InvalidRequest, "versionId is required"))?; let version_id = if version_id == "null" { version_id } else { - let parsed = Uuid::parse_str(&version_id).map_err(|_| s3_error!(InvalidArgument, "invalid local versionId"))?; + let parsed = Uuid::parse_str(&version_id) + .map_err(|_| admin_s3_error(AdminS3ErrorCode::InvalidArgument, "invalid local versionId"))?; if parsed.is_nil() { - return Err(s3_error!(InvalidArgument, "invalid local versionId")); + return Err(admin_s3_error(AdminS3ErrorCode::InvalidArgument, "invalid local versionId")); } parsed.to_string() }; @@ -1067,13 +1068,16 @@ fn validate_legacy_transition_state_reconcile_request( request_selector: &LegacyTransitionStateReconcileSelector, ) -> S3Result<()> { if !confirm { - return Err(s3_error!( - InvalidRequest, - "legacy transition-state reconciliation requires confirm=true; use GET to inspect without changes" + return Err(admin_s3_error( + AdminS3ErrorCode::InvalidRequest, + "legacy transition-state reconciliation requires confirm=true; use GET to inspect without changes", )); } if request_selector != query_selector { - return Err(s3_error!(InvalidRequest, "request selector must exactly match the query selector")); + return Err(admin_s3_error( + AdminS3ErrorCode::InvalidRequest, + "request selector must exactly match the query selector", + )); } Ok(()) } @@ -1081,20 +1085,22 @@ fn validate_legacy_transition_state_reconcile_request( fn map_legacy_transition_state_reconcile_error(err: LegacyTransitionStateReconcileError) -> S3Error { match err { LegacyTransitionStateReconcileError::InvalidSelector(_) | LegacyTransitionStateReconcileError::InvalidRequest(_) => { - s3_error!(InvalidRequest, "invalid legacy transition-state reconciliation request") + admin_s3_error(AdminS3ErrorCode::InvalidRequest, "invalid legacy transition-state reconciliation request") } LegacyTransitionStateReconcileError::StaleExpectedTuple(_) | LegacyTransitionStateReconcileError::Corrupt(_) => { - s3_error!(OperationAborted, "legacy transition-state reconciliation metadata is stale or corrupt") - } - LegacyTransitionStateReconcileError::WriteFenceUnavailable(_) => { - s3_error!( - OperationAborted, - "legacy transition-state reconciliation could not acquire safe write authority" + admin_s3_error( + AdminS3ErrorCode::OperationAborted, + "legacy transition-state reconciliation metadata is stale or corrupt", ) } - LegacyTransitionStateReconcileError::BackendUnavailable(_) => { - s3_error!(InternalError, "legacy transition-state reconciliation backend is unavailable") - } + LegacyTransitionStateReconcileError::WriteFenceUnavailable(_) => admin_s3_error( + AdminS3ErrorCode::OperationAborted, + "legacy transition-state reconciliation could not acquire safe write authority", + ), + LegacyTransitionStateReconcileError::BackendUnavailable(_) => admin_s3_error( + AdminS3ErrorCode::InternalError, + "legacy transition-state reconciliation backend is unavailable", + ), } } @@ -2066,7 +2072,7 @@ impl Operation for LegacyTransitionStateReconcileInspectHandler { authorize_transition_admin_request(&req, AdminAction::ListTierAction).await?; let selector = parse_legacy_transition_state_reconcile_query(req.uri.query())?; let Some(store) = object_store_from_extensions(&req.extensions) else { - return Err(s3_error!(InternalError, "object store is not initialized")); + return Err(admin_s3_error(AdminS3ErrorCode::InternalError, "object store is not initialized")); }; let response: LegacyTransitionStateReconcileResponse = store .inspect_legacy_transition_state(selector) @@ -2085,15 +2091,21 @@ impl Operation for LegacyTransitionStateReconcileApplyHandler { let selector = parse_legacy_transition_state_reconcile_query(req.uri.query())?; let store = object_store_from_extensions(&req.extensions); let mut input = req.input; - let body = input - .store_all_limited(MAX_ADMIN_REQUEST_BODY_SIZE) - .await - .map_err(|_| s3_error!(InvalidRequest, "legacy transition-state reconciliation body is too large or unreadable"))?; - let request: LegacyTransitionStateReconcileRequest = serde_json::from_slice(&body) - .map_err(|_| s3_error!(InvalidRequest, "legacy transition-state reconciliation request must be valid JSON"))?; + let body = input.store_all_limited(MAX_ADMIN_REQUEST_BODY_SIZE).await.map_err(|_| { + admin_s3_error( + AdminS3ErrorCode::InvalidRequest, + "legacy transition-state reconciliation body is too large or unreadable", + ) + })?; + let request: LegacyTransitionStateReconcileRequest = serde_json::from_slice(&body).map_err(|_| { + admin_s3_error( + AdminS3ErrorCode::InvalidRequest, + "legacy transition-state reconciliation request must be valid JSON", + ) + })?; validate_legacy_transition_state_reconcile_request(&selector, request.confirm, &request.selector)?; let Some(store) = store else { - return Err(s3_error!(InternalError, "object store is not initialized")); + return Err(admin_s3_error(AdminS3ErrorCode::InternalError, "object store is not initialized")); }; let response: LegacyTransitionStateReconcileResponse = store