s3: make RenameObject idempotent for a retried request (#11178)

* feat(s3): make RenameObject idempotent for a retried request - #10661

A rename that succeeds but whose response is lost leaves the client with
no safe move: retrying returned 404, because the source is already gone,
so a retry was indistinguishable from a rename that never happened.

The destination now carries what the rename that created it was, under
x-seaweedfs-rename-token: the client's token, the source key and the
time. A retry that names the same token and the same source and
destination is answered 200 without touching anything. The same token
sent for a different rename is refused with 409 rather than silently
answered, and a token older than 24 hours is treated as unrelated so a
key cannot answer for a request indefinitely.

Requests without the header behave exactly as before.

* s3: answer a reused rename token with 409, not 400

The PR promised Conflict and the code returned Bad Request. 400 tells a
client its request was malformed and invites it to give up; this request
is well formed and resending it unchanged will not help, because what it
collides with is a rename the same token already stands for.

The status code is now asserted in a test, since it is the part of this
behaviour a client actually acts on.

* Update weed/s3api/s3err/s3api_errors.go

Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* s3: fix rename token review notes

- ErrIdempotentParameterMismatch returns 409 Conflict, not 400. The
  comment and TestRenameTokenReuseAnswersConflict both expect 409; the
  code regressed to 400 in a later commit.
- stampRenameToken: clarify that markRenameToken mutates srcEntry in
  place, so the token reaches the destination via the move regardless
  of whether the UpdateEntry succeeds. The precondition only guards the
  pre-move write, not the move itself.
- Extract the handler retry branch into retryRenameDecision and add
  TestRetryRenameDecision, covering the source-still-exists fallthrough
  that was previously reasoned about but not tested.

* s3: IdempotentParameterMismatch returns 400, matching AWS docs

The AWS S3 RenameObject API documentation specifies HTTP Status Code: 400
for IdempotencyParameterMismatch. Revert the previous 409 change and align
the comment and test with the documented behavior.

---------

Co-authored-by: Chris Lu <chrislusf@users.noreply.github.com>
Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Chris Lu <chris.lu@gmail.com>
This commit is contained in:
Junker der Provinz
2026-09-05 11:48:42 -07:00
committed by GitHub
co-authored by Chris Lu devin-ai-integration[bot] Chris Lu
parent 3f9b05946b
commit f99c4a1f14
4 changed files with 316 additions and 18 deletions
+8
View File
@@ -170,6 +170,7 @@ const (
ErrInvalidRenameSource
ErrRenameDestinationSameAsSource
ErrIdempotentParameterMismatch
)
// Error message constants for checksum validation
@@ -381,6 +382,13 @@ var errorCodeResponse = map[ErrorCode]APIError{
Description: "This rename request is illegal because it is trying to rename an object to itself.",
HTTPStatusCode: http.StatusBadRequest,
},
ErrIdempotentParameterMismatch: {
Code: "IdempotentParameterMismatch",
Description: "The request uses the same client token as a previous, but non-identical request.",
// 400 Bad Request, matching the AWS S3 RenameObject API documentation
// for IdempotencyParameterMismatch.
HTTPStatusCode: http.StatusBadRequest,
},
ErrInvalidTag: {
Code: "InvalidTag",
Description: "The Tag value you have provided is invalid",