diff --git a/weed/s3api/s3api_object_handlers_delete.go b/weed/s3api/s3api_object_handlers_delete.go index 636548198..e95e63751 100644 --- a/weed/s3api/s3api_object_handlers_delete.go +++ b/weed/s3api/s3api_object_handlers_delete.go @@ -231,8 +231,10 @@ func (s3a *S3ApiServer) DeleteObjectHandler(w http.ResponseWriter, r *http.Reque case resp.ErrorCode == filer_pb.FilerError_PRECONDITION_FAILED: deleteCode, deleteHandled = s3err.ErrPreconditionFailed, true case resp.Error != "": - glog.Errorf("DeleteObjectHandler: routed delete failed for %s/%s: %s", bucket, object, resp.Error) - deleteCode, deleteHandled = s3err.ErrInternalError, true + // Fall back to the lock path, which carries extra handling the + // raw DeleteEntry lacks (e.g. demoting a directory marker that + // still has children instead of failing on a non-empty folder). + glog.Warningf("DeleteObjectHandler: routed delete to %s returned %q for %s/%s, falling back to lock", owner, resp.Error, bucket, object) default: deleteCode, deleteHandled = s3err.ErrNone, true } diff --git a/weed/s3api/s3api_object_handlers_put.go b/weed/s3api/s3api_object_handlers_put.go index 578d0ba0f..fe6086446 100644 --- a/weed/s3api/s3api_object_handlers_put.go +++ b/weed/s3api/s3api_object_handlers_put.go @@ -850,11 +850,17 @@ func (s3a *S3ApiServer) putToFiler(r *http.Request, filePath string, dataReader switch { case err != nil: glog.Warningf("putToFiler: routed create to %s failed for %s, falling back to lock: %v", owner, filePath, err) - case resp.ErrorCode == filer_pb.FilerError_PRECONDITION_FAILED: - createCode, routed = s3err.ErrPreconditionFailed, true + case resp.ErrorCode != filer_pb.FilerError_OK: + // Map known filer error codes to the same S3 errors the lock path + // would produce; fall back for any code this does not recognize. + if code, mapped := filerErrorCodeToS3Error(resp.ErrorCode); mapped { + createCode, routed = code, true + } else { + glog.Warningf("putToFiler: routed create to %s returned code %v for %s, falling back to lock", owner, resp.ErrorCode, filePath) + } case resp.Error != "": - createErr = fmt.Errorf("%s", resp.Error) - createCode, routed = filerErrorToS3Error(createErr), true + // In-band error without a code: fall back so the lock path maps it. + glog.Warningf("putToFiler: routed create to %s returned %q for %s, falling back to lock", owner, resp.Error, filePath) default: entryCreated, createCode, routed = true, s3err.ErrNone, true } diff --git a/weed/s3api/s3api_object_routed_write.go b/weed/s3api/s3api_object_routed_write.go index 4d3144410..f9e354f16 100644 --- a/weed/s3api/s3api_object_routed_write.go +++ b/weed/s3api/s3api_object_routed_write.go @@ -128,6 +128,25 @@ func (s3a *S3ApiServer) createEntryOnFiler(owner pb.ServerAddress, req *filer_pb return resp, err } +// filerErrorCodeToS3Error maps a routed response's machine-readable FilerError +// to the same S3 error the lock path produces via filerErrorToS3Error, so the +// fast path keeps identical semantics. ok is false for codes it does not map, +// signalling the caller to fall back to the lock path for exact behavior. +func filerErrorCodeToS3Error(code filer_pb.FilerError) (s3err.ErrorCode, bool) { + switch code { + case filer_pb.FilerError_PRECONDITION_FAILED: + return s3err.ErrPreconditionFailed, true + case filer_pb.FilerError_ENTRY_NAME_TOO_LONG: + return s3err.ErrKeyTooLongError, true + case filer_pb.FilerError_PARENT_IS_FILE, filer_pb.FilerError_EXISTING_IS_FILE: + return s3err.ErrExistingObjectIsFile, true + case filer_pb.FilerError_EXISTING_IS_DIRECTORY: + return s3err.ErrExistingObjectIsDirectory, true + default: + return s3err.ErrNone, false + } +} + // buildDeleteCondition reduces a DeleteObject's If-Match header to a primitive. // DeleteObject only honors If-Match (matching checkDeleteIfMatch), so other // conditional headers are ignored here as they are on the existing path.