fix: reuse admin error boundary for legacy tier reconciliation (#7587)

This commit is contained in:
Zhengchao An
2026-09-09 19:57:34 +08:00
committed by GitHub
parent 3ffc3704af
commit 0c7b6188b2
+42 -30
View File
@@ -1023,33 +1023,34 @@ struct LegacyTransitionStateReconcileQuery {
fn parse_legacy_transition_state_reconcile_query(query: Option<&str>) -> S3Result<LegacyTransitionStateReconcileSelector> {
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