diff --git a/other/java/client/src/main/proto/filer.proto b/other/java/client/src/main/proto/filer.proto index 2f6a4bd99..80d5f9952 100644 --- a/other/java/client/src/main/proto/filer.proto +++ b/other/java/client/src/main/proto/filer.proto @@ -203,9 +203,21 @@ message CreateEntryRequest { bool skip_check_parent_directory = 6; } +// Structured error codes for filer entry operations. +// Values are stable — do not reorder or reuse numbers. +enum FilerError { + OK = 0; + ENTRY_NAME_TOO_LONG = 1; // name exceeds max_file_name_length + PARENT_IS_FILE = 2; // parent path component is a file, not a directory + EXISTING_IS_DIRECTORY = 3; // cannot overwrite directory with file + EXISTING_IS_FILE = 4; // cannot overwrite file with directory + ENTRY_ALREADY_EXISTS = 5; // O_EXCL and entry already exists +} + message CreateEntryResponse { - string error = 1; + string error = 1; // kept for human readability + backward compat SubscribeMetadataResponse metadata_event = 2; + FilerError error_code = 3; // machine-readable error code } message UpdateEntryRequest { diff --git a/weed/filer/filer.go b/weed/filer/filer.go index 25fce8bf3..87ecb28c5 100644 --- a/weed/filer/filer.go +++ b/weed/filer/filer.go @@ -23,7 +23,6 @@ import ( "github.com/seaweedfs/seaweedfs/weed/glog" "github.com/seaweedfs/seaweedfs/weed/pb/filer_pb" "github.com/seaweedfs/seaweedfs/weed/util" - "github.com/seaweedfs/seaweedfs/weed/util/constants" "github.com/seaweedfs/seaweedfs/weed/util/log_buffer" "github.com/seaweedfs/seaweedfs/weed/wdclient" "golang.org/x/sync/singleflight" @@ -204,7 +203,7 @@ func (f *Filer) CreateEntry(ctx context.Context, entry *Entry, o_excl bool, isFr } if entry.FullPath.IsLongerFileName(maxFilenameLength) { - return fmt.Errorf(constants.ErrMsgEntryNameTooLong) + return filer_pb.ErrEntryNameTooLong } if entry.IsDirectory() { @@ -238,7 +237,7 @@ func (f *Filer) CreateEntry(ctx context.Context, entry *Entry, o_excl bool, isFr } else { if o_excl { glog.V(3).InfofCtx(ctx, "EEXIST: entry %s already exists", entry.FullPath) - return fmt.Errorf("EEXIST: entry %s already exists", entry.FullPath) + return fmt.Errorf("%s: %w", entry.FullPath, filer_pb.ErrEntryAlreadyExists) } glog.V(4).InfofCtx(ctx, "UpdateEntry %s: old entry: %v", entry.FullPath, oldEntry.Name()) if err := f.UpdateEntry(ctx, oldEntry, entry); err != nil { @@ -324,8 +323,16 @@ func (f *Filer) ensureParentDirectoryEntry(ctx context.Context, entry *Entry, di } } else if !dirEntry.IsDirectory() { - glog.ErrorfCtx(ctx, "CreateEntry %s: %s should be a directory", entry.FullPath, dirPath) - return fmt.Errorf("%s%s", dirPath, constants.ErrMsgIsAFile) + // S3 allows both "foo/bar" (object) and "foo/bar/xyzzy" (another + // object) to coexist because S3 has a flat key space. Promote the + // existing file to a directory, preserving its content/chunks so + // the original object data remains accessible. + glog.V(2).InfofCtx(ctx, "promoting %s from file to directory for %s", dirPath, entry.FullPath) + dirEntry.Attr.Mode |= os.ModeDir | 0111 + if updateErr := f.Store.UpdateEntry(ctx, dirEntry); updateErr != nil { + return fmt.Errorf("promote %s to directory: %v", dirPath, updateErr) + } + f.NotifyUpdateEvent(ctx, nil, dirEntry, false, isFromOtherCluster, nil) } return nil @@ -336,11 +343,11 @@ func (f *Filer) UpdateEntry(ctx context.Context, oldEntry, entry *Entry) (err er entry.Attr.Crtime = oldEntry.Attr.Crtime if oldEntry.IsDirectory() && !entry.IsDirectory() { glog.ErrorfCtx(ctx, "existing %s is a directory", oldEntry.FullPath) - return fmt.Errorf("%s%s%s", constants.ErrMsgExistingPrefix, oldEntry.FullPath, constants.ErrMsgIsADirectory) + return fmt.Errorf("%s: %w", oldEntry.FullPath, filer_pb.ErrExistingIsDirectory) } if !oldEntry.IsDirectory() && entry.IsDirectory() { glog.ErrorfCtx(ctx, "existing %s is a file", oldEntry.FullPath) - return fmt.Errorf("%s%s%s", constants.ErrMsgExistingPrefix, oldEntry.FullPath, constants.ErrMsgIsAFile) + return fmt.Errorf("%s: %w", oldEntry.FullPath, filer_pb.ErrExistingIsFile) } } return f.Store.UpdateEntry(ctx, entry) diff --git a/weed/pb/filer.proto b/weed/pb/filer.proto index 2f6a4bd99..80d5f9952 100644 --- a/weed/pb/filer.proto +++ b/weed/pb/filer.proto @@ -203,9 +203,21 @@ message CreateEntryRequest { bool skip_check_parent_directory = 6; } +// Structured error codes for filer entry operations. +// Values are stable — do not reorder or reuse numbers. +enum FilerError { + OK = 0; + ENTRY_NAME_TOO_LONG = 1; // name exceeds max_file_name_length + PARENT_IS_FILE = 2; // parent path component is a file, not a directory + EXISTING_IS_DIRECTORY = 3; // cannot overwrite directory with file + EXISTING_IS_FILE = 4; // cannot overwrite file with directory + ENTRY_ALREADY_EXISTS = 5; // O_EXCL and entry already exists +} + message CreateEntryResponse { - string error = 1; + string error = 1; // kept for human readability + backward compat SubscribeMetadataResponse metadata_event = 2; + FilerError error_code = 3; // machine-readable error code } message UpdateEntryRequest { diff --git a/weed/pb/filer_pb/filer.pb.go b/weed/pb/filer_pb/filer.pb.go index b6d39a3a7..23df9cae1 100644 --- a/weed/pb/filer_pb/filer.pb.go +++ b/weed/pb/filer_pb/filer.pb.go @@ -73,6 +73,66 @@ func (SSEType) EnumDescriptor() ([]byte, []int) { return file_filer_proto_rawDescGZIP(), []int{0} } +// Structured error codes for filer entry operations. +// Values are stable — do not reorder or reuse numbers. +type FilerError int32 + +const ( + FilerError_OK FilerError = 0 + FilerError_ENTRY_NAME_TOO_LONG FilerError = 1 // name exceeds max_file_name_length + FilerError_PARENT_IS_FILE FilerError = 2 // parent path component is a file, not a directory + FilerError_EXISTING_IS_DIRECTORY FilerError = 3 // cannot overwrite directory with file + FilerError_EXISTING_IS_FILE FilerError = 4 // cannot overwrite file with directory + FilerError_ENTRY_ALREADY_EXISTS FilerError = 5 // O_EXCL and entry already exists +) + +// Enum value maps for FilerError. +var ( + FilerError_name = map[int32]string{ + 0: "OK", + 1: "ENTRY_NAME_TOO_LONG", + 2: "PARENT_IS_FILE", + 3: "EXISTING_IS_DIRECTORY", + 4: "EXISTING_IS_FILE", + 5: "ENTRY_ALREADY_EXISTS", + } + FilerError_value = map[string]int32{ + "OK": 0, + "ENTRY_NAME_TOO_LONG": 1, + "PARENT_IS_FILE": 2, + "EXISTING_IS_DIRECTORY": 3, + "EXISTING_IS_FILE": 4, + "ENTRY_ALREADY_EXISTS": 5, + } +) + +func (x FilerError) Enum() *FilerError { + p := new(FilerError) + *p = x + return p +} + +func (x FilerError) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (FilerError) Descriptor() protoreflect.EnumDescriptor { + return file_filer_proto_enumTypes[1].Descriptor() +} + +func (FilerError) Type() protoreflect.EnumType { + return &file_filer_proto_enumTypes[1] +} + +func (x FilerError) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use FilerError.Descriptor instead. +func (FilerError) EnumDescriptor() ([]byte, []int) { + return file_filer_proto_rawDescGZIP(), []int{1} +} + type LookupDirectoryEntryRequest struct { state protoimpl.MessageState `protogen:"open.v1"` Directory string `protobuf:"bytes,1,opt,name=directory,proto3" json:"directory,omitempty"` @@ -1119,8 +1179,9 @@ func (x *CreateEntryRequest) GetSkipCheckParentDirectory() bool { type CreateEntryResponse struct { state protoimpl.MessageState `protogen:"open.v1"` - Error string `protobuf:"bytes,1,opt,name=error,proto3" json:"error,omitempty"` + Error string `protobuf:"bytes,1,opt,name=error,proto3" json:"error,omitempty"` // kept for human readability + backward compat MetadataEvent *SubscribeMetadataResponse `protobuf:"bytes,2,opt,name=metadata_event,json=metadataEvent,proto3" json:"metadata_event,omitempty"` + ErrorCode FilerError `protobuf:"varint,3,opt,name=error_code,json=errorCode,proto3,enum=filer_pb.FilerError" json:"error_code,omitempty"` // machine-readable error code unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1169,6 +1230,13 @@ func (x *CreateEntryResponse) GetMetadataEvent() *SubscribeMetadataResponse { return nil } +func (x *CreateEntryResponse) GetErrorCode() FilerError { + if x != nil { + return x.ErrorCode + } + return FilerError_OK +} + type UpdateEntryRequest struct { state protoimpl.MessageState `protogen:"open.v1"` Directory string `protobuf:"bytes,1,opt,name=directory,proto3" json:"directory,omitempty"` @@ -4571,10 +4639,12 @@ const file_filer_proto_rawDesc = "" + "\n" + "signatures\x18\x05 \x03(\x05R\n" + "signatures\x12=\n" + - "\x1bskip_check_parent_directory\x18\x06 \x01(\bR\x18skipCheckParentDirectory\"w\n" + + "\x1bskip_check_parent_directory\x18\x06 \x01(\bR\x18skipCheckParentDirectory\"\xac\x01\n" + "\x13CreateEntryResponse\x12\x14\n" + "\x05error\x18\x01 \x01(\tR\x05error\x12J\n" + - "\x0emetadata_event\x18\x02 \x01(\v2#.filer_pb.SubscribeMetadataResponseR\rmetadataEvent\"\xd2\x02\n" + + "\x0emetadata_event\x18\x02 \x01(\v2#.filer_pb.SubscribeMetadataResponseR\rmetadataEvent\x123\n" + + "\n" + + "error_code\x18\x03 \x01(\x0e2\x14.filer_pb.FilerErrorR\terrorCode\"\xd2\x02\n" + "\x12UpdateEntryRequest\x12\x1c\n" + "\tdirectory\x18\x01 \x01(\tR\tdirectory\x12%\n" + "\x05entry\x18\x02 \x01(\v2\x0f.filer_pb.EntryR\x05entry\x121\n" + @@ -4851,7 +4921,15 @@ const file_filer_proto_rawDesc = "" + "\x05SSE_C\x10\x01\x12\v\n" + "\aSSE_KMS\x10\x02\x12\n" + "\n" + - "\x06SSE_S3\x10\x032\xf7\x10\n" + + "\x06SSE_S3\x10\x03*\x8c\x01\n" + + "\n" + + "FilerError\x12\x06\n" + + "\x02OK\x10\x00\x12\x17\n" + + "\x13ENTRY_NAME_TOO_LONG\x10\x01\x12\x12\n" + + "\x0ePARENT_IS_FILE\x10\x02\x12\x19\n" + + "\x15EXISTING_IS_DIRECTORY\x10\x03\x12\x14\n" + + "\x10EXISTING_IS_FILE\x10\x04\x12\x18\n" + + "\x14ENTRY_ALREADY_EXISTS\x10\x052\xf7\x10\n" + "\fSeaweedFiler\x12g\n" + "\x14LookupDirectoryEntry\x12%.filer_pb.LookupDirectoryEntryRequest\x1a&.filer_pb.LookupDirectoryEntryResponse\"\x00\x12N\n" + "\vListEntries\x12\x1c.filer_pb.ListEntriesRequest\x1a\x1d.filer_pb.ListEntriesResponse\"\x000\x01\x12L\n" + @@ -4894,171 +4972,173 @@ func file_filer_proto_rawDescGZIP() []byte { return file_filer_proto_rawDescData } -var file_filer_proto_enumTypes = make([]protoimpl.EnumInfo, 1) +var file_filer_proto_enumTypes = make([]protoimpl.EnumInfo, 2) var file_filer_proto_msgTypes = make([]protoimpl.MessageInfo, 71) var file_filer_proto_goTypes = []any{ (SSEType)(0), // 0: filer_pb.SSEType - (*LookupDirectoryEntryRequest)(nil), // 1: filer_pb.LookupDirectoryEntryRequest - (*LookupDirectoryEntryResponse)(nil), // 2: filer_pb.LookupDirectoryEntryResponse - (*ListEntriesRequest)(nil), // 3: filer_pb.ListEntriesRequest - (*ListEntriesResponse)(nil), // 4: filer_pb.ListEntriesResponse - (*RemoteEntry)(nil), // 5: filer_pb.RemoteEntry - (*Entry)(nil), // 6: filer_pb.Entry - (*FullEntry)(nil), // 7: filer_pb.FullEntry - (*EventNotification)(nil), // 8: filer_pb.EventNotification - (*FileChunk)(nil), // 9: filer_pb.FileChunk - (*FileChunkManifest)(nil), // 10: filer_pb.FileChunkManifest - (*FileId)(nil), // 11: filer_pb.FileId - (*FuseAttributes)(nil), // 12: filer_pb.FuseAttributes - (*CreateEntryRequest)(nil), // 13: filer_pb.CreateEntryRequest - (*CreateEntryResponse)(nil), // 14: filer_pb.CreateEntryResponse - (*UpdateEntryRequest)(nil), // 15: filer_pb.UpdateEntryRequest - (*UpdateEntryResponse)(nil), // 16: filer_pb.UpdateEntryResponse - (*AppendToEntryRequest)(nil), // 17: filer_pb.AppendToEntryRequest - (*AppendToEntryResponse)(nil), // 18: filer_pb.AppendToEntryResponse - (*DeleteEntryRequest)(nil), // 19: filer_pb.DeleteEntryRequest - (*DeleteEntryResponse)(nil), // 20: filer_pb.DeleteEntryResponse - (*AtomicRenameEntryRequest)(nil), // 21: filer_pb.AtomicRenameEntryRequest - (*AtomicRenameEntryResponse)(nil), // 22: filer_pb.AtomicRenameEntryResponse - (*StreamRenameEntryRequest)(nil), // 23: filer_pb.StreamRenameEntryRequest - (*StreamRenameEntryResponse)(nil), // 24: filer_pb.StreamRenameEntryResponse - (*AssignVolumeRequest)(nil), // 25: filer_pb.AssignVolumeRequest - (*AssignVolumeResponse)(nil), // 26: filer_pb.AssignVolumeResponse - (*LookupVolumeRequest)(nil), // 27: filer_pb.LookupVolumeRequest - (*Locations)(nil), // 28: filer_pb.Locations - (*Location)(nil), // 29: filer_pb.Location - (*LookupVolumeResponse)(nil), // 30: filer_pb.LookupVolumeResponse - (*Collection)(nil), // 31: filer_pb.Collection - (*CollectionListRequest)(nil), // 32: filer_pb.CollectionListRequest - (*CollectionListResponse)(nil), // 33: filer_pb.CollectionListResponse - (*DeleteCollectionRequest)(nil), // 34: filer_pb.DeleteCollectionRequest - (*DeleteCollectionResponse)(nil), // 35: filer_pb.DeleteCollectionResponse - (*StatisticsRequest)(nil), // 36: filer_pb.StatisticsRequest - (*StatisticsResponse)(nil), // 37: filer_pb.StatisticsResponse - (*PingRequest)(nil), // 38: filer_pb.PingRequest - (*PingResponse)(nil), // 39: filer_pb.PingResponse - (*GetFilerConfigurationRequest)(nil), // 40: filer_pb.GetFilerConfigurationRequest - (*GetFilerConfigurationResponse)(nil), // 41: filer_pb.GetFilerConfigurationResponse - (*SubscribeMetadataRequest)(nil), // 42: filer_pb.SubscribeMetadataRequest - (*SubscribeMetadataResponse)(nil), // 43: filer_pb.SubscribeMetadataResponse - (*TraverseBfsMetadataRequest)(nil), // 44: filer_pb.TraverseBfsMetadataRequest - (*TraverseBfsMetadataResponse)(nil), // 45: filer_pb.TraverseBfsMetadataResponse - (*LogEntry)(nil), // 46: filer_pb.LogEntry - (*KeepConnectedRequest)(nil), // 47: filer_pb.KeepConnectedRequest - (*KeepConnectedResponse)(nil), // 48: filer_pb.KeepConnectedResponse - (*LocateBrokerRequest)(nil), // 49: filer_pb.LocateBrokerRequest - (*LocateBrokerResponse)(nil), // 50: filer_pb.LocateBrokerResponse - (*KvGetRequest)(nil), // 51: filer_pb.KvGetRequest - (*KvGetResponse)(nil), // 52: filer_pb.KvGetResponse - (*KvPutRequest)(nil), // 53: filer_pb.KvPutRequest - (*KvPutResponse)(nil), // 54: filer_pb.KvPutResponse - (*FilerConf)(nil), // 55: filer_pb.FilerConf - (*CacheRemoteObjectToLocalClusterRequest)(nil), // 56: filer_pb.CacheRemoteObjectToLocalClusterRequest - (*CacheRemoteObjectToLocalClusterResponse)(nil), // 57: filer_pb.CacheRemoteObjectToLocalClusterResponse - (*LockRequest)(nil), // 58: filer_pb.LockRequest - (*LockResponse)(nil), // 59: filer_pb.LockResponse - (*UnlockRequest)(nil), // 60: filer_pb.UnlockRequest - (*UnlockResponse)(nil), // 61: filer_pb.UnlockResponse - (*FindLockOwnerRequest)(nil), // 62: filer_pb.FindLockOwnerRequest - (*FindLockOwnerResponse)(nil), // 63: filer_pb.FindLockOwnerResponse - (*Lock)(nil), // 64: filer_pb.Lock - (*TransferLocksRequest)(nil), // 65: filer_pb.TransferLocksRequest - (*TransferLocksResponse)(nil), // 66: filer_pb.TransferLocksResponse - nil, // 67: filer_pb.Entry.ExtendedEntry - nil, // 68: filer_pb.UpdateEntryRequest.ExpectedExtendedEntry - nil, // 69: filer_pb.LookupVolumeResponse.LocationsMapEntry - (*LocateBrokerResponse_Resource)(nil), // 70: filer_pb.LocateBrokerResponse.Resource - (*FilerConf_PathConf)(nil), // 71: filer_pb.FilerConf.PathConf + (FilerError)(0), // 1: filer_pb.FilerError + (*LookupDirectoryEntryRequest)(nil), // 2: filer_pb.LookupDirectoryEntryRequest + (*LookupDirectoryEntryResponse)(nil), // 3: filer_pb.LookupDirectoryEntryResponse + (*ListEntriesRequest)(nil), // 4: filer_pb.ListEntriesRequest + (*ListEntriesResponse)(nil), // 5: filer_pb.ListEntriesResponse + (*RemoteEntry)(nil), // 6: filer_pb.RemoteEntry + (*Entry)(nil), // 7: filer_pb.Entry + (*FullEntry)(nil), // 8: filer_pb.FullEntry + (*EventNotification)(nil), // 9: filer_pb.EventNotification + (*FileChunk)(nil), // 10: filer_pb.FileChunk + (*FileChunkManifest)(nil), // 11: filer_pb.FileChunkManifest + (*FileId)(nil), // 12: filer_pb.FileId + (*FuseAttributes)(nil), // 13: filer_pb.FuseAttributes + (*CreateEntryRequest)(nil), // 14: filer_pb.CreateEntryRequest + (*CreateEntryResponse)(nil), // 15: filer_pb.CreateEntryResponse + (*UpdateEntryRequest)(nil), // 16: filer_pb.UpdateEntryRequest + (*UpdateEntryResponse)(nil), // 17: filer_pb.UpdateEntryResponse + (*AppendToEntryRequest)(nil), // 18: filer_pb.AppendToEntryRequest + (*AppendToEntryResponse)(nil), // 19: filer_pb.AppendToEntryResponse + (*DeleteEntryRequest)(nil), // 20: filer_pb.DeleteEntryRequest + (*DeleteEntryResponse)(nil), // 21: filer_pb.DeleteEntryResponse + (*AtomicRenameEntryRequest)(nil), // 22: filer_pb.AtomicRenameEntryRequest + (*AtomicRenameEntryResponse)(nil), // 23: filer_pb.AtomicRenameEntryResponse + (*StreamRenameEntryRequest)(nil), // 24: filer_pb.StreamRenameEntryRequest + (*StreamRenameEntryResponse)(nil), // 25: filer_pb.StreamRenameEntryResponse + (*AssignVolumeRequest)(nil), // 26: filer_pb.AssignVolumeRequest + (*AssignVolumeResponse)(nil), // 27: filer_pb.AssignVolumeResponse + (*LookupVolumeRequest)(nil), // 28: filer_pb.LookupVolumeRequest + (*Locations)(nil), // 29: filer_pb.Locations + (*Location)(nil), // 30: filer_pb.Location + (*LookupVolumeResponse)(nil), // 31: filer_pb.LookupVolumeResponse + (*Collection)(nil), // 32: filer_pb.Collection + (*CollectionListRequest)(nil), // 33: filer_pb.CollectionListRequest + (*CollectionListResponse)(nil), // 34: filer_pb.CollectionListResponse + (*DeleteCollectionRequest)(nil), // 35: filer_pb.DeleteCollectionRequest + (*DeleteCollectionResponse)(nil), // 36: filer_pb.DeleteCollectionResponse + (*StatisticsRequest)(nil), // 37: filer_pb.StatisticsRequest + (*StatisticsResponse)(nil), // 38: filer_pb.StatisticsResponse + (*PingRequest)(nil), // 39: filer_pb.PingRequest + (*PingResponse)(nil), // 40: filer_pb.PingResponse + (*GetFilerConfigurationRequest)(nil), // 41: filer_pb.GetFilerConfigurationRequest + (*GetFilerConfigurationResponse)(nil), // 42: filer_pb.GetFilerConfigurationResponse + (*SubscribeMetadataRequest)(nil), // 43: filer_pb.SubscribeMetadataRequest + (*SubscribeMetadataResponse)(nil), // 44: filer_pb.SubscribeMetadataResponse + (*TraverseBfsMetadataRequest)(nil), // 45: filer_pb.TraverseBfsMetadataRequest + (*TraverseBfsMetadataResponse)(nil), // 46: filer_pb.TraverseBfsMetadataResponse + (*LogEntry)(nil), // 47: filer_pb.LogEntry + (*KeepConnectedRequest)(nil), // 48: filer_pb.KeepConnectedRequest + (*KeepConnectedResponse)(nil), // 49: filer_pb.KeepConnectedResponse + (*LocateBrokerRequest)(nil), // 50: filer_pb.LocateBrokerRequest + (*LocateBrokerResponse)(nil), // 51: filer_pb.LocateBrokerResponse + (*KvGetRequest)(nil), // 52: filer_pb.KvGetRequest + (*KvGetResponse)(nil), // 53: filer_pb.KvGetResponse + (*KvPutRequest)(nil), // 54: filer_pb.KvPutRequest + (*KvPutResponse)(nil), // 55: filer_pb.KvPutResponse + (*FilerConf)(nil), // 56: filer_pb.FilerConf + (*CacheRemoteObjectToLocalClusterRequest)(nil), // 57: filer_pb.CacheRemoteObjectToLocalClusterRequest + (*CacheRemoteObjectToLocalClusterResponse)(nil), // 58: filer_pb.CacheRemoteObjectToLocalClusterResponse + (*LockRequest)(nil), // 59: filer_pb.LockRequest + (*LockResponse)(nil), // 60: filer_pb.LockResponse + (*UnlockRequest)(nil), // 61: filer_pb.UnlockRequest + (*UnlockResponse)(nil), // 62: filer_pb.UnlockResponse + (*FindLockOwnerRequest)(nil), // 63: filer_pb.FindLockOwnerRequest + (*FindLockOwnerResponse)(nil), // 64: filer_pb.FindLockOwnerResponse + (*Lock)(nil), // 65: filer_pb.Lock + (*TransferLocksRequest)(nil), // 66: filer_pb.TransferLocksRequest + (*TransferLocksResponse)(nil), // 67: filer_pb.TransferLocksResponse + nil, // 68: filer_pb.Entry.ExtendedEntry + nil, // 69: filer_pb.UpdateEntryRequest.ExpectedExtendedEntry + nil, // 70: filer_pb.LookupVolumeResponse.LocationsMapEntry + (*LocateBrokerResponse_Resource)(nil), // 71: filer_pb.LocateBrokerResponse.Resource + (*FilerConf_PathConf)(nil), // 72: filer_pb.FilerConf.PathConf } var file_filer_proto_depIdxs = []int32{ - 6, // 0: filer_pb.LookupDirectoryEntryResponse.entry:type_name -> filer_pb.Entry - 6, // 1: filer_pb.ListEntriesResponse.entry:type_name -> filer_pb.Entry - 9, // 2: filer_pb.Entry.chunks:type_name -> filer_pb.FileChunk - 12, // 3: filer_pb.Entry.attributes:type_name -> filer_pb.FuseAttributes - 67, // 4: filer_pb.Entry.extended:type_name -> filer_pb.Entry.ExtendedEntry - 5, // 5: filer_pb.Entry.remote_entry:type_name -> filer_pb.RemoteEntry - 6, // 6: filer_pb.FullEntry.entry:type_name -> filer_pb.Entry - 6, // 7: filer_pb.EventNotification.old_entry:type_name -> filer_pb.Entry - 6, // 8: filer_pb.EventNotification.new_entry:type_name -> filer_pb.Entry - 11, // 9: filer_pb.FileChunk.fid:type_name -> filer_pb.FileId - 11, // 10: filer_pb.FileChunk.source_fid:type_name -> filer_pb.FileId + 7, // 0: filer_pb.LookupDirectoryEntryResponse.entry:type_name -> filer_pb.Entry + 7, // 1: filer_pb.ListEntriesResponse.entry:type_name -> filer_pb.Entry + 10, // 2: filer_pb.Entry.chunks:type_name -> filer_pb.FileChunk + 13, // 3: filer_pb.Entry.attributes:type_name -> filer_pb.FuseAttributes + 68, // 4: filer_pb.Entry.extended:type_name -> filer_pb.Entry.ExtendedEntry + 6, // 5: filer_pb.Entry.remote_entry:type_name -> filer_pb.RemoteEntry + 7, // 6: filer_pb.FullEntry.entry:type_name -> filer_pb.Entry + 7, // 7: filer_pb.EventNotification.old_entry:type_name -> filer_pb.Entry + 7, // 8: filer_pb.EventNotification.new_entry:type_name -> filer_pb.Entry + 12, // 9: filer_pb.FileChunk.fid:type_name -> filer_pb.FileId + 12, // 10: filer_pb.FileChunk.source_fid:type_name -> filer_pb.FileId 0, // 11: filer_pb.FileChunk.sse_type:type_name -> filer_pb.SSEType - 9, // 12: filer_pb.FileChunkManifest.chunks:type_name -> filer_pb.FileChunk - 6, // 13: filer_pb.CreateEntryRequest.entry:type_name -> filer_pb.Entry - 43, // 14: filer_pb.CreateEntryResponse.metadata_event:type_name -> filer_pb.SubscribeMetadataResponse - 6, // 15: filer_pb.UpdateEntryRequest.entry:type_name -> filer_pb.Entry - 68, // 16: filer_pb.UpdateEntryRequest.expected_extended:type_name -> filer_pb.UpdateEntryRequest.ExpectedExtendedEntry - 43, // 17: filer_pb.UpdateEntryResponse.metadata_event:type_name -> filer_pb.SubscribeMetadataResponse - 9, // 18: filer_pb.AppendToEntryRequest.chunks:type_name -> filer_pb.FileChunk - 43, // 19: filer_pb.DeleteEntryResponse.metadata_event:type_name -> filer_pb.SubscribeMetadataResponse - 8, // 20: filer_pb.StreamRenameEntryResponse.event_notification:type_name -> filer_pb.EventNotification - 29, // 21: filer_pb.AssignVolumeResponse.location:type_name -> filer_pb.Location - 29, // 22: filer_pb.Locations.locations:type_name -> filer_pb.Location - 69, // 23: filer_pb.LookupVolumeResponse.locations_map:type_name -> filer_pb.LookupVolumeResponse.LocationsMapEntry - 31, // 24: filer_pb.CollectionListResponse.collections:type_name -> filer_pb.Collection - 8, // 25: filer_pb.SubscribeMetadataResponse.event_notification:type_name -> filer_pb.EventNotification - 6, // 26: filer_pb.TraverseBfsMetadataResponse.entry:type_name -> filer_pb.Entry - 70, // 27: filer_pb.LocateBrokerResponse.resources:type_name -> filer_pb.LocateBrokerResponse.Resource - 71, // 28: filer_pb.FilerConf.locations:type_name -> filer_pb.FilerConf.PathConf - 6, // 29: filer_pb.CacheRemoteObjectToLocalClusterResponse.entry:type_name -> filer_pb.Entry - 43, // 30: filer_pb.CacheRemoteObjectToLocalClusterResponse.metadata_event:type_name -> filer_pb.SubscribeMetadataResponse - 64, // 31: filer_pb.TransferLocksRequest.locks:type_name -> filer_pb.Lock - 28, // 32: filer_pb.LookupVolumeResponse.LocationsMapEntry.value:type_name -> filer_pb.Locations - 1, // 33: filer_pb.SeaweedFiler.LookupDirectoryEntry:input_type -> filer_pb.LookupDirectoryEntryRequest - 3, // 34: filer_pb.SeaweedFiler.ListEntries:input_type -> filer_pb.ListEntriesRequest - 13, // 35: filer_pb.SeaweedFiler.CreateEntry:input_type -> filer_pb.CreateEntryRequest - 15, // 36: filer_pb.SeaweedFiler.UpdateEntry:input_type -> filer_pb.UpdateEntryRequest - 17, // 37: filer_pb.SeaweedFiler.AppendToEntry:input_type -> filer_pb.AppendToEntryRequest - 19, // 38: filer_pb.SeaweedFiler.DeleteEntry:input_type -> filer_pb.DeleteEntryRequest - 21, // 39: filer_pb.SeaweedFiler.AtomicRenameEntry:input_type -> filer_pb.AtomicRenameEntryRequest - 23, // 40: filer_pb.SeaweedFiler.StreamRenameEntry:input_type -> filer_pb.StreamRenameEntryRequest - 25, // 41: filer_pb.SeaweedFiler.AssignVolume:input_type -> filer_pb.AssignVolumeRequest - 27, // 42: filer_pb.SeaweedFiler.LookupVolume:input_type -> filer_pb.LookupVolumeRequest - 32, // 43: filer_pb.SeaweedFiler.CollectionList:input_type -> filer_pb.CollectionListRequest - 34, // 44: filer_pb.SeaweedFiler.DeleteCollection:input_type -> filer_pb.DeleteCollectionRequest - 36, // 45: filer_pb.SeaweedFiler.Statistics:input_type -> filer_pb.StatisticsRequest - 38, // 46: filer_pb.SeaweedFiler.Ping:input_type -> filer_pb.PingRequest - 40, // 47: filer_pb.SeaweedFiler.GetFilerConfiguration:input_type -> filer_pb.GetFilerConfigurationRequest - 44, // 48: filer_pb.SeaweedFiler.TraverseBfsMetadata:input_type -> filer_pb.TraverseBfsMetadataRequest - 42, // 49: filer_pb.SeaweedFiler.SubscribeMetadata:input_type -> filer_pb.SubscribeMetadataRequest - 42, // 50: filer_pb.SeaweedFiler.SubscribeLocalMetadata:input_type -> filer_pb.SubscribeMetadataRequest - 51, // 51: filer_pb.SeaweedFiler.KvGet:input_type -> filer_pb.KvGetRequest - 53, // 52: filer_pb.SeaweedFiler.KvPut:input_type -> filer_pb.KvPutRequest - 56, // 53: filer_pb.SeaweedFiler.CacheRemoteObjectToLocalCluster:input_type -> filer_pb.CacheRemoteObjectToLocalClusterRequest - 58, // 54: filer_pb.SeaweedFiler.DistributedLock:input_type -> filer_pb.LockRequest - 60, // 55: filer_pb.SeaweedFiler.DistributedUnlock:input_type -> filer_pb.UnlockRequest - 62, // 56: filer_pb.SeaweedFiler.FindLockOwner:input_type -> filer_pb.FindLockOwnerRequest - 65, // 57: filer_pb.SeaweedFiler.TransferLocks:input_type -> filer_pb.TransferLocksRequest - 2, // 58: filer_pb.SeaweedFiler.LookupDirectoryEntry:output_type -> filer_pb.LookupDirectoryEntryResponse - 4, // 59: filer_pb.SeaweedFiler.ListEntries:output_type -> filer_pb.ListEntriesResponse - 14, // 60: filer_pb.SeaweedFiler.CreateEntry:output_type -> filer_pb.CreateEntryResponse - 16, // 61: filer_pb.SeaweedFiler.UpdateEntry:output_type -> filer_pb.UpdateEntryResponse - 18, // 62: filer_pb.SeaweedFiler.AppendToEntry:output_type -> filer_pb.AppendToEntryResponse - 20, // 63: filer_pb.SeaweedFiler.DeleteEntry:output_type -> filer_pb.DeleteEntryResponse - 22, // 64: filer_pb.SeaweedFiler.AtomicRenameEntry:output_type -> filer_pb.AtomicRenameEntryResponse - 24, // 65: filer_pb.SeaweedFiler.StreamRenameEntry:output_type -> filer_pb.StreamRenameEntryResponse - 26, // 66: filer_pb.SeaweedFiler.AssignVolume:output_type -> filer_pb.AssignVolumeResponse - 30, // 67: filer_pb.SeaweedFiler.LookupVolume:output_type -> filer_pb.LookupVolumeResponse - 33, // 68: filer_pb.SeaweedFiler.CollectionList:output_type -> filer_pb.CollectionListResponse - 35, // 69: filer_pb.SeaweedFiler.DeleteCollection:output_type -> filer_pb.DeleteCollectionResponse - 37, // 70: filer_pb.SeaweedFiler.Statistics:output_type -> filer_pb.StatisticsResponse - 39, // 71: filer_pb.SeaweedFiler.Ping:output_type -> filer_pb.PingResponse - 41, // 72: filer_pb.SeaweedFiler.GetFilerConfiguration:output_type -> filer_pb.GetFilerConfigurationResponse - 45, // 73: filer_pb.SeaweedFiler.TraverseBfsMetadata:output_type -> filer_pb.TraverseBfsMetadataResponse - 43, // 74: filer_pb.SeaweedFiler.SubscribeMetadata:output_type -> filer_pb.SubscribeMetadataResponse - 43, // 75: filer_pb.SeaweedFiler.SubscribeLocalMetadata:output_type -> filer_pb.SubscribeMetadataResponse - 52, // 76: filer_pb.SeaweedFiler.KvGet:output_type -> filer_pb.KvGetResponse - 54, // 77: filer_pb.SeaweedFiler.KvPut:output_type -> filer_pb.KvPutResponse - 57, // 78: filer_pb.SeaweedFiler.CacheRemoteObjectToLocalCluster:output_type -> filer_pb.CacheRemoteObjectToLocalClusterResponse - 59, // 79: filer_pb.SeaweedFiler.DistributedLock:output_type -> filer_pb.LockResponse - 61, // 80: filer_pb.SeaweedFiler.DistributedUnlock:output_type -> filer_pb.UnlockResponse - 63, // 81: filer_pb.SeaweedFiler.FindLockOwner:output_type -> filer_pb.FindLockOwnerResponse - 66, // 82: filer_pb.SeaweedFiler.TransferLocks:output_type -> filer_pb.TransferLocksResponse - 58, // [58:83] is the sub-list for method output_type - 33, // [33:58] is the sub-list for method input_type - 33, // [33:33] is the sub-list for extension type_name - 33, // [33:33] is the sub-list for extension extendee - 0, // [0:33] is the sub-list for field type_name + 10, // 12: filer_pb.FileChunkManifest.chunks:type_name -> filer_pb.FileChunk + 7, // 13: filer_pb.CreateEntryRequest.entry:type_name -> filer_pb.Entry + 44, // 14: filer_pb.CreateEntryResponse.metadata_event:type_name -> filer_pb.SubscribeMetadataResponse + 1, // 15: filer_pb.CreateEntryResponse.error_code:type_name -> filer_pb.FilerError + 7, // 16: filer_pb.UpdateEntryRequest.entry:type_name -> filer_pb.Entry + 69, // 17: filer_pb.UpdateEntryRequest.expected_extended:type_name -> filer_pb.UpdateEntryRequest.ExpectedExtendedEntry + 44, // 18: filer_pb.UpdateEntryResponse.metadata_event:type_name -> filer_pb.SubscribeMetadataResponse + 10, // 19: filer_pb.AppendToEntryRequest.chunks:type_name -> filer_pb.FileChunk + 44, // 20: filer_pb.DeleteEntryResponse.metadata_event:type_name -> filer_pb.SubscribeMetadataResponse + 9, // 21: filer_pb.StreamRenameEntryResponse.event_notification:type_name -> filer_pb.EventNotification + 30, // 22: filer_pb.AssignVolumeResponse.location:type_name -> filer_pb.Location + 30, // 23: filer_pb.Locations.locations:type_name -> filer_pb.Location + 70, // 24: filer_pb.LookupVolumeResponse.locations_map:type_name -> filer_pb.LookupVolumeResponse.LocationsMapEntry + 32, // 25: filer_pb.CollectionListResponse.collections:type_name -> filer_pb.Collection + 9, // 26: filer_pb.SubscribeMetadataResponse.event_notification:type_name -> filer_pb.EventNotification + 7, // 27: filer_pb.TraverseBfsMetadataResponse.entry:type_name -> filer_pb.Entry + 71, // 28: filer_pb.LocateBrokerResponse.resources:type_name -> filer_pb.LocateBrokerResponse.Resource + 72, // 29: filer_pb.FilerConf.locations:type_name -> filer_pb.FilerConf.PathConf + 7, // 30: filer_pb.CacheRemoteObjectToLocalClusterResponse.entry:type_name -> filer_pb.Entry + 44, // 31: filer_pb.CacheRemoteObjectToLocalClusterResponse.metadata_event:type_name -> filer_pb.SubscribeMetadataResponse + 65, // 32: filer_pb.TransferLocksRequest.locks:type_name -> filer_pb.Lock + 29, // 33: filer_pb.LookupVolumeResponse.LocationsMapEntry.value:type_name -> filer_pb.Locations + 2, // 34: filer_pb.SeaweedFiler.LookupDirectoryEntry:input_type -> filer_pb.LookupDirectoryEntryRequest + 4, // 35: filer_pb.SeaweedFiler.ListEntries:input_type -> filer_pb.ListEntriesRequest + 14, // 36: filer_pb.SeaweedFiler.CreateEntry:input_type -> filer_pb.CreateEntryRequest + 16, // 37: filer_pb.SeaweedFiler.UpdateEntry:input_type -> filer_pb.UpdateEntryRequest + 18, // 38: filer_pb.SeaweedFiler.AppendToEntry:input_type -> filer_pb.AppendToEntryRequest + 20, // 39: filer_pb.SeaweedFiler.DeleteEntry:input_type -> filer_pb.DeleteEntryRequest + 22, // 40: filer_pb.SeaweedFiler.AtomicRenameEntry:input_type -> filer_pb.AtomicRenameEntryRequest + 24, // 41: filer_pb.SeaweedFiler.StreamRenameEntry:input_type -> filer_pb.StreamRenameEntryRequest + 26, // 42: filer_pb.SeaweedFiler.AssignVolume:input_type -> filer_pb.AssignVolumeRequest + 28, // 43: filer_pb.SeaweedFiler.LookupVolume:input_type -> filer_pb.LookupVolumeRequest + 33, // 44: filer_pb.SeaweedFiler.CollectionList:input_type -> filer_pb.CollectionListRequest + 35, // 45: filer_pb.SeaweedFiler.DeleteCollection:input_type -> filer_pb.DeleteCollectionRequest + 37, // 46: filer_pb.SeaweedFiler.Statistics:input_type -> filer_pb.StatisticsRequest + 39, // 47: filer_pb.SeaweedFiler.Ping:input_type -> filer_pb.PingRequest + 41, // 48: filer_pb.SeaweedFiler.GetFilerConfiguration:input_type -> filer_pb.GetFilerConfigurationRequest + 45, // 49: filer_pb.SeaweedFiler.TraverseBfsMetadata:input_type -> filer_pb.TraverseBfsMetadataRequest + 43, // 50: filer_pb.SeaweedFiler.SubscribeMetadata:input_type -> filer_pb.SubscribeMetadataRequest + 43, // 51: filer_pb.SeaweedFiler.SubscribeLocalMetadata:input_type -> filer_pb.SubscribeMetadataRequest + 52, // 52: filer_pb.SeaweedFiler.KvGet:input_type -> filer_pb.KvGetRequest + 54, // 53: filer_pb.SeaweedFiler.KvPut:input_type -> filer_pb.KvPutRequest + 57, // 54: filer_pb.SeaweedFiler.CacheRemoteObjectToLocalCluster:input_type -> filer_pb.CacheRemoteObjectToLocalClusterRequest + 59, // 55: filer_pb.SeaweedFiler.DistributedLock:input_type -> filer_pb.LockRequest + 61, // 56: filer_pb.SeaweedFiler.DistributedUnlock:input_type -> filer_pb.UnlockRequest + 63, // 57: filer_pb.SeaweedFiler.FindLockOwner:input_type -> filer_pb.FindLockOwnerRequest + 66, // 58: filer_pb.SeaweedFiler.TransferLocks:input_type -> filer_pb.TransferLocksRequest + 3, // 59: filer_pb.SeaweedFiler.LookupDirectoryEntry:output_type -> filer_pb.LookupDirectoryEntryResponse + 5, // 60: filer_pb.SeaweedFiler.ListEntries:output_type -> filer_pb.ListEntriesResponse + 15, // 61: filer_pb.SeaweedFiler.CreateEntry:output_type -> filer_pb.CreateEntryResponse + 17, // 62: filer_pb.SeaweedFiler.UpdateEntry:output_type -> filer_pb.UpdateEntryResponse + 19, // 63: filer_pb.SeaweedFiler.AppendToEntry:output_type -> filer_pb.AppendToEntryResponse + 21, // 64: filer_pb.SeaweedFiler.DeleteEntry:output_type -> filer_pb.DeleteEntryResponse + 23, // 65: filer_pb.SeaweedFiler.AtomicRenameEntry:output_type -> filer_pb.AtomicRenameEntryResponse + 25, // 66: filer_pb.SeaweedFiler.StreamRenameEntry:output_type -> filer_pb.StreamRenameEntryResponse + 27, // 67: filer_pb.SeaweedFiler.AssignVolume:output_type -> filer_pb.AssignVolumeResponse + 31, // 68: filer_pb.SeaweedFiler.LookupVolume:output_type -> filer_pb.LookupVolumeResponse + 34, // 69: filer_pb.SeaweedFiler.CollectionList:output_type -> filer_pb.CollectionListResponse + 36, // 70: filer_pb.SeaweedFiler.DeleteCollection:output_type -> filer_pb.DeleteCollectionResponse + 38, // 71: filer_pb.SeaweedFiler.Statistics:output_type -> filer_pb.StatisticsResponse + 40, // 72: filer_pb.SeaweedFiler.Ping:output_type -> filer_pb.PingResponse + 42, // 73: filer_pb.SeaweedFiler.GetFilerConfiguration:output_type -> filer_pb.GetFilerConfigurationResponse + 46, // 74: filer_pb.SeaweedFiler.TraverseBfsMetadata:output_type -> filer_pb.TraverseBfsMetadataResponse + 44, // 75: filer_pb.SeaweedFiler.SubscribeMetadata:output_type -> filer_pb.SubscribeMetadataResponse + 44, // 76: filer_pb.SeaweedFiler.SubscribeLocalMetadata:output_type -> filer_pb.SubscribeMetadataResponse + 53, // 77: filer_pb.SeaweedFiler.KvGet:output_type -> filer_pb.KvGetResponse + 55, // 78: filer_pb.SeaweedFiler.KvPut:output_type -> filer_pb.KvPutResponse + 58, // 79: filer_pb.SeaweedFiler.CacheRemoteObjectToLocalCluster:output_type -> filer_pb.CacheRemoteObjectToLocalClusterResponse + 60, // 80: filer_pb.SeaweedFiler.DistributedLock:output_type -> filer_pb.LockResponse + 62, // 81: filer_pb.SeaweedFiler.DistributedUnlock:output_type -> filer_pb.UnlockResponse + 64, // 82: filer_pb.SeaweedFiler.FindLockOwner:output_type -> filer_pb.FindLockOwnerResponse + 67, // 83: filer_pb.SeaweedFiler.TransferLocks:output_type -> filer_pb.TransferLocksResponse + 59, // [59:84] is the sub-list for method output_type + 34, // [34:59] is the sub-list for method input_type + 34, // [34:34] is the sub-list for extension type_name + 34, // [34:34] is the sub-list for extension extendee + 0, // [0:34] is the sub-list for field type_name } func init() { file_filer_proto_init() } @@ -5071,7 +5151,7 @@ func file_filer_proto_init() { File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_filer_proto_rawDesc), len(file_filer_proto_rawDesc)), - NumEnums: 1, + NumEnums: 2, NumMessages: 71, NumExtensions: 0, NumServices: 1, diff --git a/weed/pb/filer_pb/filer_pb_helper.go b/weed/pb/filer_pb/filer_pb_helper.go index fed902824..a6d6fa10b 100644 --- a/weed/pb/filer_pb/filer_pb_helper.go +++ b/weed/pb/filer_pb/filer_pb_helper.go @@ -145,6 +145,13 @@ func CreateEntryWithResponse(ctx context.Context, client SeaweedFilerClient, req glog.V(1).InfofCtx(ctx, "create entry %s/%s %v: %v", request.Directory, request.Entry.Name, request.OExcl, err) return nil, fmt.Errorf("CreateEntry: %w", err) } + if resp.ErrorCode != FilerError_OK { + glog.V(1).InfofCtx(ctx, "create entry %s/%s %v: %v (code %v)", request.Directory, request.Entry.Name, request.OExcl, resp.Error, resp.ErrorCode) + if sentinel := FilerErrorToSentinel(resp.ErrorCode); sentinel != nil { + return nil, fmt.Errorf("CreateEntry %s/%s: %w", request.Directory, request.Entry.Name, sentinel) + } + return nil, fmt.Errorf("CreateEntry: %w", errors.New(resp.Error)) + } if resp.Error != "" { glog.V(1).InfofCtx(ctx, "create entry %s/%s %v: %v", request.Directory, request.Entry.Name, request.OExcl, resp.Error) return nil, fmt.Errorf("CreateEntry: %w", errors.New(resp.Error)) @@ -183,6 +190,37 @@ func LookupEntry(ctx context.Context, client SeaweedFilerClient, request *Lookup var ErrNotFound = errors.New("filer: no entry is found in filer store") +// Sentinel errors for filer entry operations. +// These are set by the filer and reconstructed from FilerError codes after +// crossing the gRPC boundary, so consumers can use errors.Is() instead of +// parsing error strings. +var ( + ErrEntryNameTooLong = errors.New("entry name too long") + ErrParentIsFile = errors.New("parent path is a file") + ErrExistingIsDirectory = errors.New("existing entry is a directory") + ErrExistingIsFile = errors.New("existing entry is a file") + ErrEntryAlreadyExists = errors.New("entry already exists") +) + +// FilerErrorToSentinel maps a proto FilerError code to its sentinel error. +// Returns nil for OK or unknown codes. +func FilerErrorToSentinel(code FilerError) error { + switch code { + case FilerError_ENTRY_NAME_TOO_LONG: + return ErrEntryNameTooLong + case FilerError_PARENT_IS_FILE: + return ErrParentIsFile + case FilerError_EXISTING_IS_DIRECTORY: + return ErrExistingIsDirectory + case FilerError_EXISTING_IS_FILE: + return ErrExistingIsFile + case FilerError_ENTRY_ALREADY_EXISTS: + return ErrEntryAlreadyExists + default: + return nil + } +} + func IsEmpty(event *SubscribeMetadataResponse) bool { return event.EventNotification.NewEntry == nil && event.EventNotification.OldEntry == nil } diff --git a/weed/s3api/iceberg/metadata_files.go b/weed/s3api/iceberg/metadata_files.go index 296876def..e18189040 100644 --- a/weed/s3api/iceberg/metadata_files.go +++ b/weed/s3api/iceberg/metadata_files.go @@ -51,7 +51,7 @@ func (s *Server) saveMetadataFile(ctx context.Context, bucketName, tablePath, me if createErr != nil { return fmt.Errorf("failed to create %s: %w", errorContext, createErr) } - if resp.Error != "" && !strings.Contains(resp.Error, "exist") { + if resp.ErrorCode != filer_pb.FilerError_OK && resp.ErrorCode != filer_pb.FilerError_ENTRY_ALREADY_EXISTS { return fmt.Errorf("failed to create %s: %s", errorContext, resp.Error) } return nil @@ -104,8 +104,11 @@ func (s *Server) saveMetadataFile(ctx context.Context, bucketName, tablePath, me if err != nil { return fmt.Errorf("failed to write metadata file: %w", err) } - if resp.Error != "" { - return fmt.Errorf("failed to write metadata file: %s", resp.Error) + if resp.ErrorCode != filer_pb.FilerError_OK { + if sentinel := filer_pb.FilerErrorToSentinel(resp.ErrorCode); sentinel != nil { + return fmt.Errorf("failed to write metadata file: %w", sentinel) + } + return fmt.Errorf("failed to write metadata file: code=%v %s", resp.ErrorCode, resp.Error) } return nil }) diff --git a/weed/s3api/iceberg/stage_create.go b/weed/s3api/iceberg/stage_create.go index 2ab5b6643..ccc53e166 100644 --- a/weed/s3api/iceberg/stage_create.go +++ b/weed/s3api/iceberg/stage_create.go @@ -198,7 +198,7 @@ func (s *Server) writeStageCreateMarker(ctx context.Context, bucketName string, if createErr != nil { return fmt.Errorf("failed to create %s: %w", errorContext, createErr) } - if resp.Error != "" && !strings.Contains(resp.Error, "exist") { + if resp.ErrorCode != filer_pb.FilerError_OK && resp.ErrorCode != filer_pb.FilerError_ENTRY_ALREADY_EXISTS { return fmt.Errorf("failed to create %s: %s", errorContext, resp.Error) } return nil @@ -236,8 +236,11 @@ func (s *Server) writeStageCreateMarker(ctx context.Context, bucketName string, if createErr != nil { return createErr } - if resp.Error != "" { - return errors.New(resp.Error) + if resp.ErrorCode != filer_pb.FilerError_OK { + if sentinel := filer_pb.FilerErrorToSentinel(resp.ErrorCode); sentinel != nil { + return fmt.Errorf("create stage marker: %w", sentinel) + } + return fmt.Errorf("create stage marker: code=%v %s", resp.ErrorCode, resp.Error) } return nil }) diff --git a/weed/s3api/s3_sse_s3.go b/weed/s3api/s3_sse_s3.go index a9c22e666..e869308f3 100644 --- a/weed/s3api/s3_sse_s3.go +++ b/weed/s3api/s3_sse_s3.go @@ -15,7 +15,6 @@ import ( mathrand "math/rand" "net/http" "os" - "strings" "sync" "time" @@ -359,8 +358,8 @@ func (km *SSES3KeyManager) generateAndSaveSuperKeyToFiler() error { // Set appropriate permissions for the directory entry.Attributes.FileMode = uint32(0700 | os.ModeDir) }); err != nil { - // Only ignore "file exists" errors. - if !strings.Contains(err.Error(), "file exists") { + // Only ignore "already exists" errors. + if !errors.Is(err, filer_pb.ErrEntryAlreadyExists) { return fmt.Errorf("failed to create KEK directory %s: %w", SSES3KEKDirectory, err) } glog.V(3).Infof("Parent directory %s already exists, continuing.", SSES3KEKDirectory) diff --git a/weed/s3api/s3api_object_handlers_put.go b/weed/s3api/s3api_object_handlers_put.go index b0e2ac703..bb160fa55 100644 --- a/weed/s3api/s3api_object_handlers_put.go +++ b/weed/s3api/s3api_object_handlers_put.go @@ -793,21 +793,25 @@ func filerErrorToS3Error(err error) s3err.ErrorCode { return s3err.ErrNone } - errString := err.Error() + // Filer sentinel errors — matched via errors.Is() after crossing gRPC boundary + switch { + case errors.Is(err, filer_pb.ErrEntryNameTooLong): + return s3err.ErrKeyTooLongError + case errors.Is(err, filer_pb.ErrParentIsFile), errors.Is(err, filer_pb.ErrExistingIsFile): + return s3err.ErrExistingObjectIsFile + case errors.Is(err, filer_pb.ErrExistingIsDirectory): + return s3err.ErrExistingObjectIsDirectory + case errors.Is(err, weed_server.ErrReadOnly): + return s3err.ErrAccessDenied + } + // Non-filer errors that don't go through CreateEntryResponse — string matching required + errString := err.Error() switch { case errString == constants.ErrMsgBadDigest: return s3err.ErrBadDigest - case errors.Is(err, weed_server.ErrReadOnly): - return s3err.ErrAccessDenied case strings.Contains(errString, "context canceled") || strings.Contains(errString, "code = Canceled"): return s3err.ErrInvalidRequest - case strings.Contains(errString, constants.ErrMsgExistingPrefix) && strings.HasSuffix(errString, constants.ErrMsgIsADirectory): - return s3err.ErrExistingObjectIsDirectory - case strings.HasSuffix(errString, constants.ErrMsgIsAFile): - return s3err.ErrExistingObjectIsFile - case strings.Contains(errString, constants.ErrMsgEntryNameTooLong): - return s3err.ErrKeyTooLongError default: return s3err.ErrInternalError } diff --git a/weed/s3api/s3api_object_handlers_put_test.go b/weed/s3api/s3api_object_handlers_put_test.go index ef6b9a757..dd5dee34e 100644 --- a/weed/s3api/s3api_object_handlers_put_test.go +++ b/weed/s3api/s3api_object_handlers_put_test.go @@ -10,6 +10,7 @@ import ( "testing" "github.com/gorilla/mux" + "github.com/seaweedfs/seaweedfs/weed/pb/filer_pb" "github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants" "github.com/seaweedfs/seaweedfs/weed/s3api/s3err" weed_server "github.com/seaweedfs/seaweedfs/weed/server" @@ -53,28 +54,28 @@ func TestFilerErrorToS3Error(t *testing.T) { expectedErr: s3err.ErrInvalidRequest, }, { - name: "Directory exists error", - err: errors.New("existing /path/to/file is a directory"), + name: "Directory exists error (sentinel)", + err: fmt.Errorf("CreateEntry /path: %w", filer_pb.ErrExistingIsDirectory), expectedErr: s3err.ErrExistingObjectIsDirectory, }, { - name: "Directory exists error (CreateEntry-wrapped)", - err: errors.New("CreateEntry: existing /path/to/file is a directory"), - expectedErr: s3err.ErrExistingObjectIsDirectory, + name: "Parent is file error (sentinel)", + err: fmt.Errorf("CreateEntry /path: %w", filer_pb.ErrParentIsFile), + expectedErr: s3err.ErrExistingObjectIsFile, }, { - name: "File exists error", - err: errors.New("/path/to/file is a file"), + name: "Existing is file error (sentinel)", + err: fmt.Errorf("CreateEntry /path: %w", filer_pb.ErrExistingIsFile), expectedErr: s3err.ErrExistingObjectIsFile, }, { - name: "Entry name too long error", - err: errors.New("CreateEntry: entry name too long"), + name: "Entry name too long (sentinel)", + err: fmt.Errorf("CreateEntry: %w", filer_pb.ErrEntryNameTooLong), expectedErr: s3err.ErrKeyTooLongError, }, { - name: "Entry name too long error (unwrapped)", - err: errors.New("entry name too long"), + name: "Entry name too long (bare sentinel)", + err: filer_pb.ErrEntryNameTooLong, expectedErr: s3err.ErrKeyTooLongError, }, { diff --git a/weed/server/filer_grpc_server.go b/weed/server/filer_grpc_server.go index 1b7b050c0..c79640a03 100644 --- a/weed/server/filer_grpc_server.go +++ b/weed/server/filer_grpc_server.go @@ -3,6 +3,7 @@ package weed_server import ( "bytes" "context" + "errors" "fmt" "os" "path/filepath" @@ -191,6 +192,18 @@ func (fs *FilerServer) CreateEntry(ctx context.Context, req *filer_pb.CreateEntr } else { glog.V(3).InfofCtx(ctx, "CreateEntry %s: %v", filepath.Join(req.Directory, req.Entry.Name), createErr) resp.Error = createErr.Error() + switch { + case errors.Is(createErr, filer_pb.ErrEntryNameTooLong): + resp.ErrorCode = filer_pb.FilerError_ENTRY_NAME_TOO_LONG + case errors.Is(createErr, filer_pb.ErrParentIsFile): + resp.ErrorCode = filer_pb.FilerError_PARENT_IS_FILE + case errors.Is(createErr, filer_pb.ErrExistingIsDirectory): + resp.ErrorCode = filer_pb.FilerError_EXISTING_IS_DIRECTORY + case errors.Is(createErr, filer_pb.ErrExistingIsFile): + resp.ErrorCode = filer_pb.FilerError_EXISTING_IS_FILE + case errors.Is(createErr, filer_pb.ErrEntryAlreadyExists): + resp.ErrorCode = filer_pb.FilerError_ENTRY_ALREADY_EXISTS + } } return