From 9658f309d20e499911cafd0eeed2a14538751071 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Sun, 31 May 2026 18:52:44 -0700 Subject: [PATCH] EC bitrot detection: per-shard checksum sidecars (#9761) * ec: add EC bitrot checksum protobuf EcBitrotProtection/EcShardChecksums/ChecksumAlgorithm sidecar messages, copy_ecsum_file and unsafe_ignore_sidecar fields, and a CHECKSUM scrub mode. * ec: bitrot checksum sidecar format, validation, and per-volume load Per-shard CRC32C block checksums in an optional .ecsum sidecar with a self-integrity header; validation, rolling builder, backfill primitive, and EcVolume load on mount + removal on destroy. * ec: capture per-shard checksums at encode; verify-and-exclude on rebuild WriteEcFilesWithContext returns the protection computed inline during encoding. generateMissingEcFiles verifies present inputs against the sidecar, excludes corrupt ones, regenerates in place, and re-verifies; fail-closed unless unsafe_ignore_sidecar, removing all generated outputs on failure. * ec: read-only checksum scrub with Reed-Solomon arbiter ChecksumScrub verifies each local shard against the sidecar and reconstructs flagged shards from the clean shards so stale-sidecar false positives are not reported. Wired to the gRPC CHECKSUM mode and ec.scrub -mode checksum. * ec: server-side bitrot sidecar write, copy, cleanup, and opportunistic backfill Write .ecsum at fresh encode; propagate it with copy_ecsum_file (tolerant); remove it on full delete and decode; rebuild honors unsafe_ignore_sidecar and opportunistically backfills a sidecar when all shards are reachable. * ec: volume server bitrot config flags -ec.bitrotChecksum (default on) and -ec.bitrotBlockSizeMB (default 16). * fix(ec_bitrot): bound -ec.bitrotBlockSizeMB before the int64 multiply Validate the MiB value is in [1, 1024] before multiplying by 1 MiB, so a huge flag value cannot overflow int64 and slip past the power-of-two check, and a block size cannot collapse a sidecar to a few oversized blocks. * fix(ec_bitrot): distribute the .ecsum sidecar from the worker encode path The worker EC encode wrote the generation-0 sidecar locally but never added it to shardFiles, so DistributeEcShards never shipped it and the distributed holders came up unprotected. Append it to shardFiles and map the ecsum shard type to its extension in the sender so it travels with the shards. * fix(ec_bitrot): remove orphaned sidecars when the generation is gone Gate sidecar removal on existingShardCount==0 alone rather than also requiring a stray .ecx. A sidecar whose shards have all been deleted is orphaned and must be removed even when no .ecx remains, or it leaks. .ecx/.ecj/.vif removal stays gated on hasEcxFile as before. * fix(ec_bitrot): do not fold checksum blocks scanned into TotalFiles ChecksumScrub's first return is blocks scanned, not files. Discard it so the scrub response's TotalFiles (a needle/file count) is not inflated by the block count for CHECKSUM mode. * test(ec_bitrot): clean up generated .ecsum sidecars in removeGeneratedFiles * fix(ec_bitrot): reject an oversized sidecar payload before the uint32 cast The header stores payload_len as a uint32; bound the payload before the conversion so a pathological manifest cannot truncate the length field and corrupt the sidecar. A real manifest is a few KB, so this never trips. * fix(ec_bitrot): cap -ec.bitrotBlockSizeMB at 64 MiB The block size becomes the per-shard scratch buffer the scrub/backfill path allocates, so an over-large value (e.g. 1 GiB) is a memory hazard per concurrent scrub worker. Lower the upper bound from 1024 to 64 MiB. * fix(ec_bitrot): add -ecUnsafeIgnoreSidecar to weed tool fix -ecx The -ecx recovery path reconstructs missing shards via RebuildEcFilesWithContext, which fails closed on a malformed/stale .ecsum. Without an override flag an operator could not complete the rebuild without manually deleting the sidecar. Expose -ecUnsafeIgnoreSidecar (default false) and thread it through. * fix(ec_bitrot): bound sidecar payload with a direct int constant; drop readFull Guard len(payload) against a plain int constant (1 GiB) before the allocation instead of a uint64 MaxUint32 compare, so the allocation-size value is provably bounded (clears the CodeQL overflow alert) and the math import is no longer needed. Inline os.File.ReadAt with io.EOF handling in verifyShardFileBlocks and remove the now-redundant readFull helper (os.File.ReadAt fills the slice or errors). * test(ec_bitrot): use slices.Contains instead of a hand-rolled containsU32 * refactor(ec): fold the EcFiles WithContext variants into the base functions RebuildEcFiles now takes the *ECContext directly (nil => derive from .vif as before) and WriteEcFiles takes it too (nil => default), removing the parallel RebuildEcFilesWithContext / WriteEcFilesWithContext names. Callers that had an explicit context drop the WithContext suffix; the default-context callers pass nil. No behavior change. * refactor(ec): pass BackgroundECContext instead of nil to Write/RebuildEcFiles Add a non-nil BackgroundECContext placeholder (analogous to context.Background()) and have callers with no specific layout pass it instead of a nil *ECContext. WriteEcFiles resolves a zero/background context to the default ratio and RebuildEcFiles resolves it from the .vif, so behavior is unchanged. * fix(ec_bitrot): make BackgroundECContext a func; RebuildEcFiles fails closed on bad .vif - BackgroundECContext is now a function returning a fresh *ECContext, so callers cannot mutate a shared singleton or race on it (and it mirrors context.Background, which is also a function). - RebuildEcFiles now propagates the MaybeLoadVolumeInfo error: a present-but- unreadable .vif fails closed instead of silently rebuilding with the default ratio (which would corrupt a custom-ratio volume). Pass an explicit ctx to override. --- weed/command/fix.go | 19 +- weed/command/fix_ecx_test.go | 2 +- weed/command/volume.go | 24 + weed/pb/volume_server.proto | 27 + weed/pb/volume_server_pb/volume_server.pb.go | 970 +++++++++++------- weed/server/volume_grpc_erasure_coding.go | 116 ++- weed/server/volume_grpc_scrub.go | 6 + weed/shell/command_ec_common.go | 1 + weed/shell/command_ec_scrub.go | 4 +- .../disk_location_ec_realworld_test.go | 4 +- weed/storage/erasure_coding/ec_bitrot.go | 554 ++++++++++ weed/storage/erasure_coding/ec_bitrot_test.go | 362 +++++++ .../erasure_coding/ec_consistency_test.go | 6 +- weed/storage/erasure_coding/ec_context.go | 11 + weed/storage/erasure_coding/ec_encoder.go | 287 +++++- .../erasure_coding/ec_roundtrip_test.go | 6 +- weed/storage/erasure_coding/ec_test.go | 3 +- weed/storage/erasure_coding/ec_volume.go | 16 + .../storage/erasure_coding/ec_volume_scrub.go | 185 ++++ .../erasure_coding/shard_distribution.go | 6 + weed/storage/remote_tier_integration_test.go | 7 +- weed/worker/tasks/erasure_coding/ec_task.go | 25 +- 22 files changed, 2184 insertions(+), 457 deletions(-) create mode 100644 weed/storage/erasure_coding/ec_bitrot.go create mode 100644 weed/storage/erasure_coding/ec_bitrot_test.go diff --git a/weed/command/fix.go b/weed/command/fix.go index 7edbee27e..e92f67a47 100644 --- a/weed/command/fix.go +++ b/weed/command/fix.go @@ -37,14 +37,15 @@ var cmdFix = &Command{ } var ( - fixVolumeCollection = cmdFix.Flag.String("collection", "", "an optional volume collection name, if specified only it will be processed") - fixVolumeId = cmdFix.Flag.Int64("volumeId", 0, "an optional volume id, if not 0 (default) only it will be processed") - fixIncludeDeleted = cmdFix.Flag.Bool("includeDeleted", true, "include deleted entries in the index file") - fixIgnoreError = cmdFix.Flag.Bool("ignoreError", false, "an optional, if true will be processed despite errors") - fixRemoteFile = cmdFix.Flag.Bool("remoteFile", false, "an optional, if true will not try to load the local .dat file, but only the remote file") - fixGenerateEcx = cmdFix.Flag.Bool("ecx", false, "regenerate a lost EC index (.ecx) — and the .vif when missing — from the local .ec## shards (missing shards are reconstructed from parity when enough survive). Run with the volume server stopped.") - fixEcDataShards = cmdFix.Flag.Int("ecDataShards", 0, "EC data shard count for -ecx (0 = read from .vif, otherwise default 10)") - fixEcParityShards = cmdFix.Flag.Int("ecParityShards", 0, "EC parity shard count for -ecx (0 = read from .vif, infer from shard count, otherwise default 4)") + fixVolumeCollection = cmdFix.Flag.String("collection", "", "an optional volume collection name, if specified only it will be processed") + fixVolumeId = cmdFix.Flag.Int64("volumeId", 0, "an optional volume id, if not 0 (default) only it will be processed") + fixIncludeDeleted = cmdFix.Flag.Bool("includeDeleted", true, "include deleted entries in the index file") + fixIgnoreError = cmdFix.Flag.Bool("ignoreError", false, "an optional, if true will be processed despite errors") + fixRemoteFile = cmdFix.Flag.Bool("remoteFile", false, "an optional, if true will not try to load the local .dat file, but only the remote file") + fixGenerateEcx = cmdFix.Flag.Bool("ecx", false, "regenerate a lost EC index (.ecx) — and the .vif when missing — from the local .ec## shards (missing shards are reconstructed from parity when enough survive). Run with the volume server stopped.") + fixEcDataShards = cmdFix.Flag.Int("ecDataShards", 0, "EC data shard count for -ecx (0 = read from .vif, otherwise default 10)") + fixEcParityShards = cmdFix.Flag.Int("ecParityShards", 0, "EC parity shard count for -ecx (0 = read from .vif, infer from shard count, otherwise default 4)") + fixEcUnsafeIgnoreSidecar = cmdFix.Flag.Bool("ecUnsafeIgnoreSidecar", false, "for -ecx: proceed even when the EC bitrot checksum sidecar (.ecsum) is malformed or stale, instead of failing closed; the reconstructed shards are not verified against it") ) type VolumeFileScanner4Fix struct { @@ -381,7 +382,7 @@ func doFixEcxFromShards(basePath, baseFileName, collection string, volumeId int6 if !dataComplete { ctx := &erasure_coding.ECContext{DataShards: dataShards, ParityShards: parityShards} glog.Infof("volume %d: %d/%d shards present; reconstructing missing shards (%s) before index rebuild", volumeId, presentCount, dataShards+parityShards, ctx.String()) - if _, err := erasure_coding.RebuildEcFilesWithContext(base, ctx); err != nil { + if _, err := erasure_coding.RebuildEcFiles(base, ctx, *fixEcUnsafeIgnoreSidecar); err != nil { fail(fmt.Errorf("volume %d: reconstruct missing shards from %d survivors: %w", volumeId, presentCount, err)) return } diff --git a/weed/command/fix_ecx_test.go b/weed/command/fix_ecx_test.go index d47254d31..d99f82694 100644 --- a/weed/command/fix_ecx_test.go +++ b/weed/command/fix_ecx_test.go @@ -92,7 +92,7 @@ func buildAndEncodeTestEcVolume(t *testing.T, dir, baseName string) (base string idxFile.Close() nm.Close() - if err := erasure_coding.WriteEcFiles(base); err != nil { + if _, err := erasure_coding.WriteEcFiles(base, erasure_coding.BackgroundECContext()); err != nil { t.Fatalf("WriteEcFiles: %v", err) } if err := erasure_coding.WriteSortedFileFromIdx(base, ".ecx"); err != nil { diff --git a/weed/command/volume.go b/weed/command/volume.go index b7c9113f3..c2969c9c8 100644 --- a/weed/command/volume.go +++ b/weed/command/volume.go @@ -23,6 +23,7 @@ import ( "github.com/seaweedfs/seaweedfs/weed/server/constants" stats_collect "github.com/seaweedfs/seaweedfs/weed/stats" "github.com/seaweedfs/seaweedfs/weed/storage" + "github.com/seaweedfs/seaweedfs/weed/storage/erasure_coding" "github.com/seaweedfs/seaweedfs/weed/storage/types" "github.com/seaweedfs/seaweedfs/weed/util" "github.com/seaweedfs/seaweedfs/weed/util/grace" @@ -139,6 +140,9 @@ var ( volumeWhiteListOption = cmdVolume.Flag.String("whiteList", "", "comma separated Ip addresses having write permission. No limit if empty.") minFreeSpacePercent = cmdVolume.Flag.String("minFreeSpacePercent", "1", "minimum free disk space (default to 1%). Low disk space will mark all volumes as ReadOnly (deprecated, use minFreeSpace instead).") minFreeSpace = cmdVolume.Flag.String("minFreeSpace", "", "min free disk space (value<=100 as percentage like 1, other as human readable bytes, like 10GiB). Low disk space will mark all volumes as ReadOnly.") + + ecBitrotChecksum = cmdVolume.Flag.Bool("ec.bitrotChecksum", true, "write a bitrot checksum sidecar (.ecsum) when generating EC shards, enabling silent-corruption detection on scrub/rebuild") + ecBitrotBlockSizeMB = cmdVolume.Flag.Int("ec.bitrotBlockSizeMB", 16, "EC bitrot checksum block granularity in MiB; must be a power of two and at least 1") ) func runVolume(cmd *Command, args []string) bool { @@ -171,6 +175,26 @@ func runVolume(cmd *Command, args []string) bool { *v.mastersString = *v.mserverString } + // Apply EC bitrot checksum settings. + erasure_coding.BitrotProtectionEnabled = *ecBitrotChecksum + // Validate the block size before multiplying so an absurd MiB value cannot + // overflow int64 and slip a bogus size past the power-of-two check. The + // block size also becomes the per-shard scratch buffer the scrub/backfill + // path allocates, so the upper bound caps that allocation (64 MiB per + // concurrent scrub worker) and keeps a typo from taking the server down. + const maxBitrotBlockSizeMB = 64 + if mb := *ecBitrotBlockSizeMB; mb >= 1 && mb <= maxBitrotBlockSizeMB { + if blockSize := int64(mb) * 1024 * 1024; blockSize&(blockSize-1) == 0 { + erasure_coding.BitrotBlockSize = blockSize + } else if mb != 16 { + glog.Warningf("ignoring invalid -ec.bitrotBlockSizeMB=%d (must be a power of two); using %d MiB", + mb, erasure_coding.BitrotBlockSize/(1024*1024)) + } + } else if *ecBitrotBlockSizeMB != 16 { + glog.Warningf("ignoring out-of-range -ec.bitrotBlockSizeMB=%d (must be a power of two in [1, %d] MiB); using %d MiB", + *ecBitrotBlockSizeMB, maxBitrotBlockSizeMB, erasure_coding.BitrotBlockSize/(1024*1024)) + } + minFreeSpaces := util.MustParseMinFreeSpace(*minFreeSpace, *minFreeSpacePercent) v.masters = pb.ServerAddresses(*v.mastersString).ToAddresses() v.startVolumeServer(*volumeFolders, *maxVolumeCounts, *volumeWhiteListOption, minFreeSpaces) diff --git a/weed/pb/volume_server.proto b/weed/pb/volume_server.proto index c63177cb4..4f71016d4 100644 --- a/weed/pb/volume_server.proto +++ b/weed/pb/volume_server.proto @@ -428,6 +428,7 @@ message VolumeEcShardsGenerateResponse { message VolumeEcShardsRebuildRequest { uint32 volume_id = 1; string collection = 2; + bool unsafe_ignore_sidecar = 3; // bypass the bitrot-sidecar fail-closed guard (operator override; distinct from ec.rebuild -force) } message VolumeEcShardsRebuildResponse { repeated uint32 rebuilt_shard_ids = 1; @@ -442,6 +443,7 @@ message VolumeEcShardsCopyRequest { bool copy_ecj_file = 6; bool copy_vif_file = 7; uint32 disk_id = 8; // Target disk ID for storing EC shards + bool copy_ecsum_file = 9; // copy the bitrot checksum sidecar (.ecsum) when present; tolerant of a missing source (no-op), since this non-2PC path has no Prepare backstop } message VolumeEcShardsCopyResponse { } @@ -578,6 +580,30 @@ message EcShardConfig { uint32 data_shards = 1; // Number of data shards (e.g., 10) uint32 parity_shards = 2; // Number of parity shards (e.g., 4) } + +// EcBitrotProtection is the entire content of a bitrot checksum sidecar +// (.ecsum for the legacy generation, .ecsum.v for vacuum +// generation N). On disk it is wrapped in a fixed header carrying a CRC32C +// over this serialized payload (see weed/storage/erasure_coding/ec_bitrot.go). +message EcBitrotProtection { + ChecksumAlgorithm algorithm = 1; // CRC32C (Castagnoli) + uint32 block_size = 2; // bytes per checksum block; default 16777216 (16 MiB), a power-of-two multiple of 1 MiB + uint32 generation = 3; // EC vacuum generation these checksums describe (0 = legacy/fresh); must match the sidecar filename version + EcShardConfig ec_shard_config = 4; // data/parity shard counts at encode time + repeated EcShardChecksums shards = 5; // one entry per shard id in the active layout + bytes encode_uuid = 6; // random per-encode identity, for stale-sidecar detection across in-place re-encodes +} + +message EcShardChecksums { + uint32 shard_id = 1; // 0..MaxShardCount-1 (custom EC ratios go up to 32) + int64 covered_size = 2; // shard byte length these checksums cover (must equal the on-disk shard length) + bytes block_crc32c = 3; // packed little-endian uint32[] = ceil(covered_size/block_size) entries +} + +enum ChecksumAlgorithm { + CHECKSUM_NONE = 0; + CHECKSUM_CRC32C = 1; +} message OldVersionVolumeInfo { repeated RemoteFile files = 1; uint32 version = 2; @@ -655,6 +681,7 @@ enum VolumeScrubMode { INDEX = 1; FULL = 2; LOCAL = 3; + CHECKSUM = 4; // EC only: verify each local shard's raw bytes against the bitrot checksum sidecar } message ScrubVolumeRequest { diff --git a/weed/pb/volume_server_pb/volume_server.pb.go b/weed/pb/volume_server_pb/volume_server.pb.go index 4d2d37df5..c05774b06 100644 --- a/weed/pb/volume_server_pb/volume_server.pb.go +++ b/weed/pb/volume_server_pb/volume_server.pb.go @@ -22,13 +22,60 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) +type ChecksumAlgorithm int32 + +const ( + ChecksumAlgorithm_CHECKSUM_NONE ChecksumAlgorithm = 0 + ChecksumAlgorithm_CHECKSUM_CRC32C ChecksumAlgorithm = 1 +) + +// Enum value maps for ChecksumAlgorithm. +var ( + ChecksumAlgorithm_name = map[int32]string{ + 0: "CHECKSUM_NONE", + 1: "CHECKSUM_CRC32C", + } + ChecksumAlgorithm_value = map[string]int32{ + "CHECKSUM_NONE": 0, + "CHECKSUM_CRC32C": 1, + } +) + +func (x ChecksumAlgorithm) Enum() *ChecksumAlgorithm { + p := new(ChecksumAlgorithm) + *p = x + return p +} + +func (x ChecksumAlgorithm) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (ChecksumAlgorithm) Descriptor() protoreflect.EnumDescriptor { + return file_volume_server_proto_enumTypes[0].Descriptor() +} + +func (ChecksumAlgorithm) Type() protoreflect.EnumType { + return &file_volume_server_proto_enumTypes[0] +} + +func (x ChecksumAlgorithm) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use ChecksumAlgorithm.Descriptor instead. +func (ChecksumAlgorithm) EnumDescriptor() ([]byte, []int) { + return file_volume_server_proto_rawDescGZIP(), []int{0} +} + type VolumeScrubMode int32 const ( - VolumeScrubMode_UNKNOWN VolumeScrubMode = 0 - VolumeScrubMode_INDEX VolumeScrubMode = 1 - VolumeScrubMode_FULL VolumeScrubMode = 2 - VolumeScrubMode_LOCAL VolumeScrubMode = 3 + VolumeScrubMode_UNKNOWN VolumeScrubMode = 0 + VolumeScrubMode_INDEX VolumeScrubMode = 1 + VolumeScrubMode_FULL VolumeScrubMode = 2 + VolumeScrubMode_LOCAL VolumeScrubMode = 3 + VolumeScrubMode_CHECKSUM VolumeScrubMode = 4 // EC only: verify each local shard's raw bytes against the bitrot checksum sidecar ) // Enum value maps for VolumeScrubMode. @@ -38,12 +85,14 @@ var ( 1: "INDEX", 2: "FULL", 3: "LOCAL", + 4: "CHECKSUM", } VolumeScrubMode_value = map[string]int32{ - "UNKNOWN": 0, - "INDEX": 1, - "FULL": 2, - "LOCAL": 3, + "UNKNOWN": 0, + "INDEX": 1, + "FULL": 2, + "LOCAL": 3, + "CHECKSUM": 4, } ) @@ -58,11 +107,11 @@ func (x VolumeScrubMode) String() string { } func (VolumeScrubMode) Descriptor() protoreflect.EnumDescriptor { - return file_volume_server_proto_enumTypes[0].Descriptor() + return file_volume_server_proto_enumTypes[1].Descriptor() } func (VolumeScrubMode) Type() protoreflect.EnumType { - return &file_volume_server_proto_enumTypes[0] + return &file_volume_server_proto_enumTypes[1] } func (x VolumeScrubMode) Number() protoreflect.EnumNumber { @@ -71,7 +120,7 @@ func (x VolumeScrubMode) Number() protoreflect.EnumNumber { // Deprecated: Use VolumeScrubMode.Descriptor instead. func (VolumeScrubMode) EnumDescriptor() ([]byte, []int) { - return file_volume_server_proto_rawDescGZIP(), []int{0} + return file_volume_server_proto_rawDescGZIP(), []int{1} } // Persistent state for volume servers. @@ -3304,11 +3353,12 @@ func (*VolumeEcShardsGenerateResponse) Descriptor() ([]byte, []int) { } type VolumeEcShardsRebuildRequest struct { - state protoimpl.MessageState `protogen:"open.v1"` - VolumeId uint32 `protobuf:"varint,1,opt,name=volume_id,json=volumeId,proto3" json:"volume_id,omitempty"` - Collection string `protobuf:"bytes,2,opt,name=collection,proto3" json:"collection,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + VolumeId uint32 `protobuf:"varint,1,opt,name=volume_id,json=volumeId,proto3" json:"volume_id,omitempty"` + Collection string `protobuf:"bytes,2,opt,name=collection,proto3" json:"collection,omitempty"` + UnsafeIgnoreSidecar bool `protobuf:"varint,3,opt,name=unsafe_ignore_sidecar,json=unsafeIgnoreSidecar,proto3" json:"unsafe_ignore_sidecar,omitempty"` // bypass the bitrot-sidecar fail-closed guard (operator override; distinct from ec.rebuild -force) + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *VolumeEcShardsRebuildRequest) Reset() { @@ -3355,6 +3405,13 @@ func (x *VolumeEcShardsRebuildRequest) GetCollection() string { return "" } +func (x *VolumeEcShardsRebuildRequest) GetUnsafeIgnoreSidecar() bool { + if x != nil { + return x.UnsafeIgnoreSidecar + } + return false +} + type VolumeEcShardsRebuildResponse struct { state protoimpl.MessageState `protogen:"open.v1"` RebuiltShardIds []uint32 `protobuf:"varint,1,rep,packed,name=rebuilt_shard_ids,json=rebuiltShardIds,proto3" json:"rebuilt_shard_ids,omitempty"` @@ -3408,7 +3465,8 @@ type VolumeEcShardsCopyRequest struct { SourceDataNode string `protobuf:"bytes,5,opt,name=source_data_node,json=sourceDataNode,proto3" json:"source_data_node,omitempty"` CopyEcjFile bool `protobuf:"varint,6,opt,name=copy_ecj_file,json=copyEcjFile,proto3" json:"copy_ecj_file,omitempty"` CopyVifFile bool `protobuf:"varint,7,opt,name=copy_vif_file,json=copyVifFile,proto3" json:"copy_vif_file,omitempty"` - DiskId uint32 `protobuf:"varint,8,opt,name=disk_id,json=diskId,proto3" json:"disk_id,omitempty"` // Target disk ID for storing EC shards + DiskId uint32 `protobuf:"varint,8,opt,name=disk_id,json=diskId,proto3" json:"disk_id,omitempty"` // Target disk ID for storing EC shards + CopyEcsumFile bool `protobuf:"varint,9,opt,name=copy_ecsum_file,json=copyEcsumFile,proto3" json:"copy_ecsum_file,omitempty"` // copy the bitrot checksum sidecar (.ecsum) when present; tolerant of a missing source (no-op), since this non-2PC path has no Prepare backstop unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -3499,6 +3557,13 @@ func (x *VolumeEcShardsCopyRequest) GetDiskId() uint32 { return 0 } +func (x *VolumeEcShardsCopyRequest) GetCopyEcsumFile() bool { + if x != nil { + return x.CopyEcsumFile + } + return false +} + type VolumeEcShardsCopyResponse struct { state protoimpl.MessageState `protogen:"open.v1"` unknownFields protoimpl.UnknownFields @@ -4921,6 +4986,154 @@ func (x *EcShardConfig) GetParityShards() uint32 { return 0 } +// EcBitrotProtection is the entire content of a bitrot checksum sidecar +// (.ecsum for the legacy generation, .ecsum.v for vacuum +// generation N). On disk it is wrapped in a fixed header carrying a CRC32C +// over this serialized payload (see weed/storage/erasure_coding/ec_bitrot.go). +type EcBitrotProtection struct { + state protoimpl.MessageState `protogen:"open.v1"` + Algorithm ChecksumAlgorithm `protobuf:"varint,1,opt,name=algorithm,proto3,enum=volume_server_pb.ChecksumAlgorithm" json:"algorithm,omitempty"` // CRC32C (Castagnoli) + BlockSize uint32 `protobuf:"varint,2,opt,name=block_size,json=blockSize,proto3" json:"block_size,omitempty"` // bytes per checksum block; default 16777216 (16 MiB), a power-of-two multiple of 1 MiB + Generation uint32 `protobuf:"varint,3,opt,name=generation,proto3" json:"generation,omitempty"` // EC vacuum generation these checksums describe (0 = legacy/fresh); must match the sidecar filename version + EcShardConfig *EcShardConfig `protobuf:"bytes,4,opt,name=ec_shard_config,json=ecShardConfig,proto3" json:"ec_shard_config,omitempty"` // data/parity shard counts at encode time + Shards []*EcShardChecksums `protobuf:"bytes,5,rep,name=shards,proto3" json:"shards,omitempty"` // one entry per shard id in the active layout + EncodeUuid []byte `protobuf:"bytes,6,opt,name=encode_uuid,json=encodeUuid,proto3" json:"encode_uuid,omitempty"` // random per-encode identity, for stale-sidecar detection across in-place re-encodes + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *EcBitrotProtection) Reset() { + *x = EcBitrotProtection{} + mi := &file_volume_server_proto_msgTypes[86] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *EcBitrotProtection) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EcBitrotProtection) ProtoMessage() {} + +func (x *EcBitrotProtection) ProtoReflect() protoreflect.Message { + mi := &file_volume_server_proto_msgTypes[86] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EcBitrotProtection.ProtoReflect.Descriptor instead. +func (*EcBitrotProtection) Descriptor() ([]byte, []int) { + return file_volume_server_proto_rawDescGZIP(), []int{86} +} + +func (x *EcBitrotProtection) GetAlgorithm() ChecksumAlgorithm { + if x != nil { + return x.Algorithm + } + return ChecksumAlgorithm_CHECKSUM_NONE +} + +func (x *EcBitrotProtection) GetBlockSize() uint32 { + if x != nil { + return x.BlockSize + } + return 0 +} + +func (x *EcBitrotProtection) GetGeneration() uint32 { + if x != nil { + return x.Generation + } + return 0 +} + +func (x *EcBitrotProtection) GetEcShardConfig() *EcShardConfig { + if x != nil { + return x.EcShardConfig + } + return nil +} + +func (x *EcBitrotProtection) GetShards() []*EcShardChecksums { + if x != nil { + return x.Shards + } + return nil +} + +func (x *EcBitrotProtection) GetEncodeUuid() []byte { + if x != nil { + return x.EncodeUuid + } + return nil +} + +type EcShardChecksums struct { + state protoimpl.MessageState `protogen:"open.v1"` + ShardId uint32 `protobuf:"varint,1,opt,name=shard_id,json=shardId,proto3" json:"shard_id,omitempty"` // 0..MaxShardCount-1 (custom EC ratios go up to 32) + CoveredSize int64 `protobuf:"varint,2,opt,name=covered_size,json=coveredSize,proto3" json:"covered_size,omitempty"` // shard byte length these checksums cover (must equal the on-disk shard length) + BlockCrc32C []byte `protobuf:"bytes,3,opt,name=block_crc32c,json=blockCrc32c,proto3" json:"block_crc32c,omitempty"` // packed little-endian uint32[] = ceil(covered_size/block_size) entries + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *EcShardChecksums) Reset() { + *x = EcShardChecksums{} + mi := &file_volume_server_proto_msgTypes[87] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *EcShardChecksums) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*EcShardChecksums) ProtoMessage() {} + +func (x *EcShardChecksums) ProtoReflect() protoreflect.Message { + mi := &file_volume_server_proto_msgTypes[87] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use EcShardChecksums.ProtoReflect.Descriptor instead. +func (*EcShardChecksums) Descriptor() ([]byte, []int) { + return file_volume_server_proto_rawDescGZIP(), []int{87} +} + +func (x *EcShardChecksums) GetShardId() uint32 { + if x != nil { + return x.ShardId + } + return 0 +} + +func (x *EcShardChecksums) GetCoveredSize() int64 { + if x != nil { + return x.CoveredSize + } + return 0 +} + +func (x *EcShardChecksums) GetBlockCrc32C() []byte { + if x != nil { + return x.BlockCrc32C + } + return nil +} + type OldVersionVolumeInfo struct { state protoimpl.MessageState `protogen:"open.v1"` Files []*RemoteFile `protobuf:"bytes,1,rep,name=files,proto3" json:"files,omitempty"` @@ -4936,7 +5149,7 @@ type OldVersionVolumeInfo struct { func (x *OldVersionVolumeInfo) Reset() { *x = OldVersionVolumeInfo{} - mi := &file_volume_server_proto_msgTypes[86] + mi := &file_volume_server_proto_msgTypes[88] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -4948,7 +5161,7 @@ func (x *OldVersionVolumeInfo) String() string { func (*OldVersionVolumeInfo) ProtoMessage() {} func (x *OldVersionVolumeInfo) ProtoReflect() protoreflect.Message { - mi := &file_volume_server_proto_msgTypes[86] + mi := &file_volume_server_proto_msgTypes[88] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -4961,7 +5174,7 @@ func (x *OldVersionVolumeInfo) ProtoReflect() protoreflect.Message { // Deprecated: Use OldVersionVolumeInfo.ProtoReflect.Descriptor instead. func (*OldVersionVolumeInfo) Descriptor() ([]byte, []int) { - return file_volume_server_proto_rawDescGZIP(), []int{86} + return file_volume_server_proto_rawDescGZIP(), []int{88} } func (x *OldVersionVolumeInfo) GetFiles() []*RemoteFile { @@ -5026,7 +5239,7 @@ type VolumeTierMoveDatToRemoteRequest struct { func (x *VolumeTierMoveDatToRemoteRequest) Reset() { *x = VolumeTierMoveDatToRemoteRequest{} - mi := &file_volume_server_proto_msgTypes[87] + mi := &file_volume_server_proto_msgTypes[89] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5038,7 +5251,7 @@ func (x *VolumeTierMoveDatToRemoteRequest) String() string { func (*VolumeTierMoveDatToRemoteRequest) ProtoMessage() {} func (x *VolumeTierMoveDatToRemoteRequest) ProtoReflect() protoreflect.Message { - mi := &file_volume_server_proto_msgTypes[87] + mi := &file_volume_server_proto_msgTypes[89] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5051,7 +5264,7 @@ func (x *VolumeTierMoveDatToRemoteRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use VolumeTierMoveDatToRemoteRequest.ProtoReflect.Descriptor instead. func (*VolumeTierMoveDatToRemoteRequest) Descriptor() ([]byte, []int) { - return file_volume_server_proto_rawDescGZIP(), []int{87} + return file_volume_server_proto_rawDescGZIP(), []int{89} } func (x *VolumeTierMoveDatToRemoteRequest) GetVolumeId() uint32 { @@ -5092,7 +5305,7 @@ type VolumeTierMoveDatToRemoteResponse struct { func (x *VolumeTierMoveDatToRemoteResponse) Reset() { *x = VolumeTierMoveDatToRemoteResponse{} - mi := &file_volume_server_proto_msgTypes[88] + mi := &file_volume_server_proto_msgTypes[90] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5104,7 +5317,7 @@ func (x *VolumeTierMoveDatToRemoteResponse) String() string { func (*VolumeTierMoveDatToRemoteResponse) ProtoMessage() {} func (x *VolumeTierMoveDatToRemoteResponse) ProtoReflect() protoreflect.Message { - mi := &file_volume_server_proto_msgTypes[88] + mi := &file_volume_server_proto_msgTypes[90] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5117,7 +5330,7 @@ func (x *VolumeTierMoveDatToRemoteResponse) ProtoReflect() protoreflect.Message // Deprecated: Use VolumeTierMoveDatToRemoteResponse.ProtoReflect.Descriptor instead. func (*VolumeTierMoveDatToRemoteResponse) Descriptor() ([]byte, []int) { - return file_volume_server_proto_rawDescGZIP(), []int{88} + return file_volume_server_proto_rawDescGZIP(), []int{90} } func (x *VolumeTierMoveDatToRemoteResponse) GetProcessed() int64 { @@ -5145,7 +5358,7 @@ type VolumeTierMoveDatFromRemoteRequest struct { func (x *VolumeTierMoveDatFromRemoteRequest) Reset() { *x = VolumeTierMoveDatFromRemoteRequest{} - mi := &file_volume_server_proto_msgTypes[89] + mi := &file_volume_server_proto_msgTypes[91] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5157,7 +5370,7 @@ func (x *VolumeTierMoveDatFromRemoteRequest) String() string { func (*VolumeTierMoveDatFromRemoteRequest) ProtoMessage() {} func (x *VolumeTierMoveDatFromRemoteRequest) ProtoReflect() protoreflect.Message { - mi := &file_volume_server_proto_msgTypes[89] + mi := &file_volume_server_proto_msgTypes[91] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5170,7 +5383,7 @@ func (x *VolumeTierMoveDatFromRemoteRequest) ProtoReflect() protoreflect.Message // Deprecated: Use VolumeTierMoveDatFromRemoteRequest.ProtoReflect.Descriptor instead. func (*VolumeTierMoveDatFromRemoteRequest) Descriptor() ([]byte, []int) { - return file_volume_server_proto_rawDescGZIP(), []int{89} + return file_volume_server_proto_rawDescGZIP(), []int{91} } func (x *VolumeTierMoveDatFromRemoteRequest) GetVolumeId() uint32 { @@ -5204,7 +5417,7 @@ type VolumeTierMoveDatFromRemoteResponse struct { func (x *VolumeTierMoveDatFromRemoteResponse) Reset() { *x = VolumeTierMoveDatFromRemoteResponse{} - mi := &file_volume_server_proto_msgTypes[90] + mi := &file_volume_server_proto_msgTypes[92] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5216,7 +5429,7 @@ func (x *VolumeTierMoveDatFromRemoteResponse) String() string { func (*VolumeTierMoveDatFromRemoteResponse) ProtoMessage() {} func (x *VolumeTierMoveDatFromRemoteResponse) ProtoReflect() protoreflect.Message { - mi := &file_volume_server_proto_msgTypes[90] + mi := &file_volume_server_proto_msgTypes[92] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5229,7 +5442,7 @@ func (x *VolumeTierMoveDatFromRemoteResponse) ProtoReflect() protoreflect.Messag // Deprecated: Use VolumeTierMoveDatFromRemoteResponse.ProtoReflect.Descriptor instead. func (*VolumeTierMoveDatFromRemoteResponse) Descriptor() ([]byte, []int) { - return file_volume_server_proto_rawDescGZIP(), []int{90} + return file_volume_server_proto_rawDescGZIP(), []int{92} } func (x *VolumeTierMoveDatFromRemoteResponse) GetProcessed() int64 { @@ -5254,7 +5467,7 @@ type VolumeServerStatusRequest struct { func (x *VolumeServerStatusRequest) Reset() { *x = VolumeServerStatusRequest{} - mi := &file_volume_server_proto_msgTypes[91] + mi := &file_volume_server_proto_msgTypes[93] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5266,7 +5479,7 @@ func (x *VolumeServerStatusRequest) String() string { func (*VolumeServerStatusRequest) ProtoMessage() {} func (x *VolumeServerStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_volume_server_proto_msgTypes[91] + mi := &file_volume_server_proto_msgTypes[93] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5279,7 +5492,7 @@ func (x *VolumeServerStatusRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use VolumeServerStatusRequest.ProtoReflect.Descriptor instead. func (*VolumeServerStatusRequest) Descriptor() ([]byte, []int) { - return file_volume_server_proto_rawDescGZIP(), []int{91} + return file_volume_server_proto_rawDescGZIP(), []int{93} } type VolumeServerStatusResponse struct { @@ -5296,7 +5509,7 @@ type VolumeServerStatusResponse struct { func (x *VolumeServerStatusResponse) Reset() { *x = VolumeServerStatusResponse{} - mi := &file_volume_server_proto_msgTypes[92] + mi := &file_volume_server_proto_msgTypes[94] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5308,7 +5521,7 @@ func (x *VolumeServerStatusResponse) String() string { func (*VolumeServerStatusResponse) ProtoMessage() {} func (x *VolumeServerStatusResponse) ProtoReflect() protoreflect.Message { - mi := &file_volume_server_proto_msgTypes[92] + mi := &file_volume_server_proto_msgTypes[94] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5321,7 +5534,7 @@ func (x *VolumeServerStatusResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use VolumeServerStatusResponse.ProtoReflect.Descriptor instead. func (*VolumeServerStatusResponse) Descriptor() ([]byte, []int) { - return file_volume_server_proto_rawDescGZIP(), []int{92} + return file_volume_server_proto_rawDescGZIP(), []int{94} } func (x *VolumeServerStatusResponse) GetDiskStatuses() []*DiskStatus { @@ -5374,7 +5587,7 @@ type VolumeServerLeaveRequest struct { func (x *VolumeServerLeaveRequest) Reset() { *x = VolumeServerLeaveRequest{} - mi := &file_volume_server_proto_msgTypes[93] + mi := &file_volume_server_proto_msgTypes[95] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5386,7 +5599,7 @@ func (x *VolumeServerLeaveRequest) String() string { func (*VolumeServerLeaveRequest) ProtoMessage() {} func (x *VolumeServerLeaveRequest) ProtoReflect() protoreflect.Message { - mi := &file_volume_server_proto_msgTypes[93] + mi := &file_volume_server_proto_msgTypes[95] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5399,7 +5612,7 @@ func (x *VolumeServerLeaveRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use VolumeServerLeaveRequest.ProtoReflect.Descriptor instead. func (*VolumeServerLeaveRequest) Descriptor() ([]byte, []int) { - return file_volume_server_proto_rawDescGZIP(), []int{93} + return file_volume_server_proto_rawDescGZIP(), []int{95} } type VolumeServerLeaveResponse struct { @@ -5410,7 +5623,7 @@ type VolumeServerLeaveResponse struct { func (x *VolumeServerLeaveResponse) Reset() { *x = VolumeServerLeaveResponse{} - mi := &file_volume_server_proto_msgTypes[94] + mi := &file_volume_server_proto_msgTypes[96] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5422,7 +5635,7 @@ func (x *VolumeServerLeaveResponse) String() string { func (*VolumeServerLeaveResponse) ProtoMessage() {} func (x *VolumeServerLeaveResponse) ProtoReflect() protoreflect.Message { - mi := &file_volume_server_proto_msgTypes[94] + mi := &file_volume_server_proto_msgTypes[96] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5435,7 +5648,7 @@ func (x *VolumeServerLeaveResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use VolumeServerLeaveResponse.ProtoReflect.Descriptor instead. func (*VolumeServerLeaveResponse) Descriptor() ([]byte, []int) { - return file_volume_server_proto_rawDescGZIP(), []int{94} + return file_volume_server_proto_rawDescGZIP(), []int{96} } // remote storage @@ -5458,7 +5671,7 @@ type FetchAndWriteNeedleRequest struct { func (x *FetchAndWriteNeedleRequest) Reset() { *x = FetchAndWriteNeedleRequest{} - mi := &file_volume_server_proto_msgTypes[95] + mi := &file_volume_server_proto_msgTypes[97] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5470,7 +5683,7 @@ func (x *FetchAndWriteNeedleRequest) String() string { func (*FetchAndWriteNeedleRequest) ProtoMessage() {} func (x *FetchAndWriteNeedleRequest) ProtoReflect() protoreflect.Message { - mi := &file_volume_server_proto_msgTypes[95] + mi := &file_volume_server_proto_msgTypes[97] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5483,7 +5696,7 @@ func (x *FetchAndWriteNeedleRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use FetchAndWriteNeedleRequest.ProtoReflect.Descriptor instead. func (*FetchAndWriteNeedleRequest) Descriptor() ([]byte, []int) { - return file_volume_server_proto_rawDescGZIP(), []int{95} + return file_volume_server_proto_rawDescGZIP(), []int{97} } func (x *FetchAndWriteNeedleRequest) GetVolumeId() uint32 { @@ -5565,7 +5778,7 @@ type FetchAndWriteNeedleResponse struct { func (x *FetchAndWriteNeedleResponse) Reset() { *x = FetchAndWriteNeedleResponse{} - mi := &file_volume_server_proto_msgTypes[96] + mi := &file_volume_server_proto_msgTypes[98] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5577,7 +5790,7 @@ func (x *FetchAndWriteNeedleResponse) String() string { func (*FetchAndWriteNeedleResponse) ProtoMessage() {} func (x *FetchAndWriteNeedleResponse) ProtoReflect() protoreflect.Message { - mi := &file_volume_server_proto_msgTypes[96] + mi := &file_volume_server_proto_msgTypes[98] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5590,7 +5803,7 @@ func (x *FetchAndWriteNeedleResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use FetchAndWriteNeedleResponse.ProtoReflect.Descriptor instead. func (*FetchAndWriteNeedleResponse) Descriptor() ([]byte, []int) { - return file_volume_server_proto_rawDescGZIP(), []int{96} + return file_volume_server_proto_rawDescGZIP(), []int{98} } func (x *FetchAndWriteNeedleResponse) GetETag() string { @@ -5612,7 +5825,7 @@ type ScrubVolumeRequest struct { func (x *ScrubVolumeRequest) Reset() { *x = ScrubVolumeRequest{} - mi := &file_volume_server_proto_msgTypes[97] + mi := &file_volume_server_proto_msgTypes[99] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5624,7 +5837,7 @@ func (x *ScrubVolumeRequest) String() string { func (*ScrubVolumeRequest) ProtoMessage() {} func (x *ScrubVolumeRequest) ProtoReflect() protoreflect.Message { - mi := &file_volume_server_proto_msgTypes[97] + mi := &file_volume_server_proto_msgTypes[99] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5637,7 +5850,7 @@ func (x *ScrubVolumeRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ScrubVolumeRequest.ProtoReflect.Descriptor instead. func (*ScrubVolumeRequest) Descriptor() ([]byte, []int) { - return file_volume_server_proto_rawDescGZIP(), []int{97} + return file_volume_server_proto_rawDescGZIP(), []int{99} } func (x *ScrubVolumeRequest) GetMode() VolumeScrubMode { @@ -5673,7 +5886,7 @@ type ScrubVolumeResponse struct { func (x *ScrubVolumeResponse) Reset() { *x = ScrubVolumeResponse{} - mi := &file_volume_server_proto_msgTypes[98] + mi := &file_volume_server_proto_msgTypes[100] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5685,7 +5898,7 @@ func (x *ScrubVolumeResponse) String() string { func (*ScrubVolumeResponse) ProtoMessage() {} func (x *ScrubVolumeResponse) ProtoReflect() protoreflect.Message { - mi := &file_volume_server_proto_msgTypes[98] + mi := &file_volume_server_proto_msgTypes[100] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5698,7 +5911,7 @@ func (x *ScrubVolumeResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ScrubVolumeResponse.ProtoReflect.Descriptor instead. func (*ScrubVolumeResponse) Descriptor() ([]byte, []int) { - return file_volume_server_proto_rawDescGZIP(), []int{98} + return file_volume_server_proto_rawDescGZIP(), []int{100} } func (x *ScrubVolumeResponse) GetTotalVolumes() uint64 { @@ -5740,7 +5953,7 @@ type ScrubEcVolumeRequest struct { func (x *ScrubEcVolumeRequest) Reset() { *x = ScrubEcVolumeRequest{} - mi := &file_volume_server_proto_msgTypes[99] + mi := &file_volume_server_proto_msgTypes[101] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5752,7 +5965,7 @@ func (x *ScrubEcVolumeRequest) String() string { func (*ScrubEcVolumeRequest) ProtoMessage() {} func (x *ScrubEcVolumeRequest) ProtoReflect() protoreflect.Message { - mi := &file_volume_server_proto_msgTypes[99] + mi := &file_volume_server_proto_msgTypes[101] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5765,7 +5978,7 @@ func (x *ScrubEcVolumeRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use ScrubEcVolumeRequest.ProtoReflect.Descriptor instead. func (*ScrubEcVolumeRequest) Descriptor() ([]byte, []int) { - return file_volume_server_proto_rawDescGZIP(), []int{99} + return file_volume_server_proto_rawDescGZIP(), []int{101} } func (x *ScrubEcVolumeRequest) GetMode() VolumeScrubMode { @@ -5795,7 +6008,7 @@ type ScrubEcVolumeResponse struct { func (x *ScrubEcVolumeResponse) Reset() { *x = ScrubEcVolumeResponse{} - mi := &file_volume_server_proto_msgTypes[100] + mi := &file_volume_server_proto_msgTypes[102] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5807,7 +6020,7 @@ func (x *ScrubEcVolumeResponse) String() string { func (*ScrubEcVolumeResponse) ProtoMessage() {} func (x *ScrubEcVolumeResponse) ProtoReflect() protoreflect.Message { - mi := &file_volume_server_proto_msgTypes[100] + mi := &file_volume_server_proto_msgTypes[102] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5820,7 +6033,7 @@ func (x *ScrubEcVolumeResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use ScrubEcVolumeResponse.ProtoReflect.Descriptor instead. func (*ScrubEcVolumeResponse) Descriptor() ([]byte, []int) { - return file_volume_server_proto_rawDescGZIP(), []int{100} + return file_volume_server_proto_rawDescGZIP(), []int{102} } func (x *ScrubEcVolumeResponse) GetTotalVolumes() uint64 { @@ -5872,7 +6085,7 @@ type QueryRequest struct { func (x *QueryRequest) Reset() { *x = QueryRequest{} - mi := &file_volume_server_proto_msgTypes[101] + mi := &file_volume_server_proto_msgTypes[103] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5884,7 +6097,7 @@ func (x *QueryRequest) String() string { func (*QueryRequest) ProtoMessage() {} func (x *QueryRequest) ProtoReflect() protoreflect.Message { - mi := &file_volume_server_proto_msgTypes[101] + mi := &file_volume_server_proto_msgTypes[103] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5897,7 +6110,7 @@ func (x *QueryRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use QueryRequest.ProtoReflect.Descriptor instead. func (*QueryRequest) Descriptor() ([]byte, []int) { - return file_volume_server_proto_rawDescGZIP(), []int{101} + return file_volume_server_proto_rawDescGZIP(), []int{103} } func (x *QueryRequest) GetSelections() []string { @@ -5944,7 +6157,7 @@ type QueriedStripe struct { func (x *QueriedStripe) Reset() { *x = QueriedStripe{} - mi := &file_volume_server_proto_msgTypes[102] + mi := &file_volume_server_proto_msgTypes[104] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -5956,7 +6169,7 @@ func (x *QueriedStripe) String() string { func (*QueriedStripe) ProtoMessage() {} func (x *QueriedStripe) ProtoReflect() protoreflect.Message { - mi := &file_volume_server_proto_msgTypes[102] + mi := &file_volume_server_proto_msgTypes[104] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -5969,7 +6182,7 @@ func (x *QueriedStripe) ProtoReflect() protoreflect.Message { // Deprecated: Use QueriedStripe.ProtoReflect.Descriptor instead. func (*QueriedStripe) Descriptor() ([]byte, []int) { - return file_volume_server_proto_rawDescGZIP(), []int{102} + return file_volume_server_proto_rawDescGZIP(), []int{104} } func (x *QueriedStripe) GetRecords() []byte { @@ -5989,7 +6202,7 @@ type VolumeNeedleStatusRequest struct { func (x *VolumeNeedleStatusRequest) Reset() { *x = VolumeNeedleStatusRequest{} - mi := &file_volume_server_proto_msgTypes[103] + mi := &file_volume_server_proto_msgTypes[105] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6001,7 +6214,7 @@ func (x *VolumeNeedleStatusRequest) String() string { func (*VolumeNeedleStatusRequest) ProtoMessage() {} func (x *VolumeNeedleStatusRequest) ProtoReflect() protoreflect.Message { - mi := &file_volume_server_proto_msgTypes[103] + mi := &file_volume_server_proto_msgTypes[105] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6014,7 +6227,7 @@ func (x *VolumeNeedleStatusRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use VolumeNeedleStatusRequest.ProtoReflect.Descriptor instead. func (*VolumeNeedleStatusRequest) Descriptor() ([]byte, []int) { - return file_volume_server_proto_rawDescGZIP(), []int{103} + return file_volume_server_proto_rawDescGZIP(), []int{105} } func (x *VolumeNeedleStatusRequest) GetVolumeId() uint32 { @@ -6045,7 +6258,7 @@ type VolumeNeedleStatusResponse struct { func (x *VolumeNeedleStatusResponse) Reset() { *x = VolumeNeedleStatusResponse{} - mi := &file_volume_server_proto_msgTypes[104] + mi := &file_volume_server_proto_msgTypes[106] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6057,7 +6270,7 @@ func (x *VolumeNeedleStatusResponse) String() string { func (*VolumeNeedleStatusResponse) ProtoMessage() {} func (x *VolumeNeedleStatusResponse) ProtoReflect() protoreflect.Message { - mi := &file_volume_server_proto_msgTypes[104] + mi := &file_volume_server_proto_msgTypes[106] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6070,7 +6283,7 @@ func (x *VolumeNeedleStatusResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use VolumeNeedleStatusResponse.ProtoReflect.Descriptor instead. func (*VolumeNeedleStatusResponse) Descriptor() ([]byte, []int) { - return file_volume_server_proto_rawDescGZIP(), []int{104} + return file_volume_server_proto_rawDescGZIP(), []int{106} } func (x *VolumeNeedleStatusResponse) GetNeedleId() uint64 { @@ -6125,7 +6338,7 @@ type PingRequest struct { func (x *PingRequest) Reset() { *x = PingRequest{} - mi := &file_volume_server_proto_msgTypes[105] + mi := &file_volume_server_proto_msgTypes[107] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6137,7 +6350,7 @@ func (x *PingRequest) String() string { func (*PingRequest) ProtoMessage() {} func (x *PingRequest) ProtoReflect() protoreflect.Message { - mi := &file_volume_server_proto_msgTypes[105] + mi := &file_volume_server_proto_msgTypes[107] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6150,7 +6363,7 @@ func (x *PingRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use PingRequest.ProtoReflect.Descriptor instead. func (*PingRequest) Descriptor() ([]byte, []int) { - return file_volume_server_proto_rawDescGZIP(), []int{105} + return file_volume_server_proto_rawDescGZIP(), []int{107} } func (x *PingRequest) GetTarget() string { @@ -6178,7 +6391,7 @@ type PingResponse struct { func (x *PingResponse) Reset() { *x = PingResponse{} - mi := &file_volume_server_proto_msgTypes[106] + mi := &file_volume_server_proto_msgTypes[108] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6190,7 +6403,7 @@ func (x *PingResponse) String() string { func (*PingResponse) ProtoMessage() {} func (x *PingResponse) ProtoReflect() protoreflect.Message { - mi := &file_volume_server_proto_msgTypes[106] + mi := &file_volume_server_proto_msgTypes[108] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6203,7 +6416,7 @@ func (x *PingResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use PingResponse.ProtoReflect.Descriptor instead. func (*PingResponse) Descriptor() ([]byte, []int) { - return file_volume_server_proto_rawDescGZIP(), []int{106} + return file_volume_server_proto_rawDescGZIP(), []int{108} } func (x *PingResponse) GetStartTimeNs() int64 { @@ -6238,7 +6451,7 @@ type FetchAndWriteNeedleRequest_Replica struct { func (x *FetchAndWriteNeedleRequest_Replica) Reset() { *x = FetchAndWriteNeedleRequest_Replica{} - mi := &file_volume_server_proto_msgTypes[107] + mi := &file_volume_server_proto_msgTypes[109] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6250,7 +6463,7 @@ func (x *FetchAndWriteNeedleRequest_Replica) String() string { func (*FetchAndWriteNeedleRequest_Replica) ProtoMessage() {} func (x *FetchAndWriteNeedleRequest_Replica) ProtoReflect() protoreflect.Message { - mi := &file_volume_server_proto_msgTypes[107] + mi := &file_volume_server_proto_msgTypes[109] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6263,7 +6476,7 @@ func (x *FetchAndWriteNeedleRequest_Replica) ProtoReflect() protoreflect.Message // Deprecated: Use FetchAndWriteNeedleRequest_Replica.ProtoReflect.Descriptor instead. func (*FetchAndWriteNeedleRequest_Replica) Descriptor() ([]byte, []int) { - return file_volume_server_proto_rawDescGZIP(), []int{95, 0} + return file_volume_server_proto_rawDescGZIP(), []int{97, 0} } func (x *FetchAndWriteNeedleRequest_Replica) GetUrl() string { @@ -6298,7 +6511,7 @@ type QueryRequest_Filter struct { func (x *QueryRequest_Filter) Reset() { *x = QueryRequest_Filter{} - mi := &file_volume_server_proto_msgTypes[108] + mi := &file_volume_server_proto_msgTypes[110] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6310,7 +6523,7 @@ func (x *QueryRequest_Filter) String() string { func (*QueryRequest_Filter) ProtoMessage() {} func (x *QueryRequest_Filter) ProtoReflect() protoreflect.Message { - mi := &file_volume_server_proto_msgTypes[108] + mi := &file_volume_server_proto_msgTypes[110] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6323,7 +6536,7 @@ func (x *QueryRequest_Filter) ProtoReflect() protoreflect.Message { // Deprecated: Use QueryRequest_Filter.ProtoReflect.Descriptor instead. func (*QueryRequest_Filter) Descriptor() ([]byte, []int) { - return file_volume_server_proto_rawDescGZIP(), []int{101, 0} + return file_volume_server_proto_rawDescGZIP(), []int{103, 0} } func (x *QueryRequest_Filter) GetField() string { @@ -6360,7 +6573,7 @@ type QueryRequest_InputSerialization struct { func (x *QueryRequest_InputSerialization) Reset() { *x = QueryRequest_InputSerialization{} - mi := &file_volume_server_proto_msgTypes[109] + mi := &file_volume_server_proto_msgTypes[111] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6372,7 +6585,7 @@ func (x *QueryRequest_InputSerialization) String() string { func (*QueryRequest_InputSerialization) ProtoMessage() {} func (x *QueryRequest_InputSerialization) ProtoReflect() protoreflect.Message { - mi := &file_volume_server_proto_msgTypes[109] + mi := &file_volume_server_proto_msgTypes[111] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6385,7 +6598,7 @@ func (x *QueryRequest_InputSerialization) ProtoReflect() protoreflect.Message { // Deprecated: Use QueryRequest_InputSerialization.ProtoReflect.Descriptor instead. func (*QueryRequest_InputSerialization) Descriptor() ([]byte, []int) { - return file_volume_server_proto_rawDescGZIP(), []int{101, 1} + return file_volume_server_proto_rawDescGZIP(), []int{103, 1} } func (x *QueryRequest_InputSerialization) GetCompressionType() string { @@ -6426,7 +6639,7 @@ type QueryRequest_OutputSerialization struct { func (x *QueryRequest_OutputSerialization) Reset() { *x = QueryRequest_OutputSerialization{} - mi := &file_volume_server_proto_msgTypes[110] + mi := &file_volume_server_proto_msgTypes[112] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6438,7 +6651,7 @@ func (x *QueryRequest_OutputSerialization) String() string { func (*QueryRequest_OutputSerialization) ProtoMessage() {} func (x *QueryRequest_OutputSerialization) ProtoReflect() protoreflect.Message { - mi := &file_volume_server_proto_msgTypes[110] + mi := &file_volume_server_proto_msgTypes[112] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6451,7 +6664,7 @@ func (x *QueryRequest_OutputSerialization) ProtoReflect() protoreflect.Message { // Deprecated: Use QueryRequest_OutputSerialization.ProtoReflect.Descriptor instead. func (*QueryRequest_OutputSerialization) Descriptor() ([]byte, []int) { - return file_volume_server_proto_rawDescGZIP(), []int{101, 2} + return file_volume_server_proto_rawDescGZIP(), []int{103, 2} } func (x *QueryRequest_OutputSerialization) GetCsvOutput() *QueryRequest_OutputSerialization_CSVOutput { @@ -6484,7 +6697,7 @@ type QueryRequest_InputSerialization_CSVInput struct { func (x *QueryRequest_InputSerialization_CSVInput) Reset() { *x = QueryRequest_InputSerialization_CSVInput{} - mi := &file_volume_server_proto_msgTypes[111] + mi := &file_volume_server_proto_msgTypes[113] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6496,7 +6709,7 @@ func (x *QueryRequest_InputSerialization_CSVInput) String() string { func (*QueryRequest_InputSerialization_CSVInput) ProtoMessage() {} func (x *QueryRequest_InputSerialization_CSVInput) ProtoReflect() protoreflect.Message { - mi := &file_volume_server_proto_msgTypes[111] + mi := &file_volume_server_proto_msgTypes[113] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6509,7 +6722,7 @@ func (x *QueryRequest_InputSerialization_CSVInput) ProtoReflect() protoreflect.M // Deprecated: Use QueryRequest_InputSerialization_CSVInput.ProtoReflect.Descriptor instead. func (*QueryRequest_InputSerialization_CSVInput) Descriptor() ([]byte, []int) { - return file_volume_server_proto_rawDescGZIP(), []int{101, 1, 0} + return file_volume_server_proto_rawDescGZIP(), []int{103, 1, 0} } func (x *QueryRequest_InputSerialization_CSVInput) GetFileHeaderInfo() string { @@ -6570,7 +6783,7 @@ type QueryRequest_InputSerialization_JSONInput struct { func (x *QueryRequest_InputSerialization_JSONInput) Reset() { *x = QueryRequest_InputSerialization_JSONInput{} - mi := &file_volume_server_proto_msgTypes[112] + mi := &file_volume_server_proto_msgTypes[114] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6582,7 +6795,7 @@ func (x *QueryRequest_InputSerialization_JSONInput) String() string { func (*QueryRequest_InputSerialization_JSONInput) ProtoMessage() {} func (x *QueryRequest_InputSerialization_JSONInput) ProtoReflect() protoreflect.Message { - mi := &file_volume_server_proto_msgTypes[112] + mi := &file_volume_server_proto_msgTypes[114] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6595,7 +6808,7 @@ func (x *QueryRequest_InputSerialization_JSONInput) ProtoReflect() protoreflect. // Deprecated: Use QueryRequest_InputSerialization_JSONInput.ProtoReflect.Descriptor instead. func (*QueryRequest_InputSerialization_JSONInput) Descriptor() ([]byte, []int) { - return file_volume_server_proto_rawDescGZIP(), []int{101, 1, 1} + return file_volume_server_proto_rawDescGZIP(), []int{103, 1, 1} } func (x *QueryRequest_InputSerialization_JSONInput) GetType() string { @@ -6613,7 +6826,7 @@ type QueryRequest_InputSerialization_ParquetInput struct { func (x *QueryRequest_InputSerialization_ParquetInput) Reset() { *x = QueryRequest_InputSerialization_ParquetInput{} - mi := &file_volume_server_proto_msgTypes[113] + mi := &file_volume_server_proto_msgTypes[115] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6625,7 +6838,7 @@ func (x *QueryRequest_InputSerialization_ParquetInput) String() string { func (*QueryRequest_InputSerialization_ParquetInput) ProtoMessage() {} func (x *QueryRequest_InputSerialization_ParquetInput) ProtoReflect() protoreflect.Message { - mi := &file_volume_server_proto_msgTypes[113] + mi := &file_volume_server_proto_msgTypes[115] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6638,7 +6851,7 @@ func (x *QueryRequest_InputSerialization_ParquetInput) ProtoReflect() protorefle // Deprecated: Use QueryRequest_InputSerialization_ParquetInput.ProtoReflect.Descriptor instead. func (*QueryRequest_InputSerialization_ParquetInput) Descriptor() ([]byte, []int) { - return file_volume_server_proto_rawDescGZIP(), []int{101, 1, 2} + return file_volume_server_proto_rawDescGZIP(), []int{103, 1, 2} } type QueryRequest_OutputSerialization_CSVOutput struct { @@ -6654,7 +6867,7 @@ type QueryRequest_OutputSerialization_CSVOutput struct { func (x *QueryRequest_OutputSerialization_CSVOutput) Reset() { *x = QueryRequest_OutputSerialization_CSVOutput{} - mi := &file_volume_server_proto_msgTypes[114] + mi := &file_volume_server_proto_msgTypes[116] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6666,7 +6879,7 @@ func (x *QueryRequest_OutputSerialization_CSVOutput) String() string { func (*QueryRequest_OutputSerialization_CSVOutput) ProtoMessage() {} func (x *QueryRequest_OutputSerialization_CSVOutput) ProtoReflect() protoreflect.Message { - mi := &file_volume_server_proto_msgTypes[114] + mi := &file_volume_server_proto_msgTypes[116] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6679,7 +6892,7 @@ func (x *QueryRequest_OutputSerialization_CSVOutput) ProtoReflect() protoreflect // Deprecated: Use QueryRequest_OutputSerialization_CSVOutput.ProtoReflect.Descriptor instead. func (*QueryRequest_OutputSerialization_CSVOutput) Descriptor() ([]byte, []int) { - return file_volume_server_proto_rawDescGZIP(), []int{101, 2, 0} + return file_volume_server_proto_rawDescGZIP(), []int{103, 2, 0} } func (x *QueryRequest_OutputSerialization_CSVOutput) GetQuoteFields() string { @@ -6726,7 +6939,7 @@ type QueryRequest_OutputSerialization_JSONOutput struct { func (x *QueryRequest_OutputSerialization_JSONOutput) Reset() { *x = QueryRequest_OutputSerialization_JSONOutput{} - mi := &file_volume_server_proto_msgTypes[115] + mi := &file_volume_server_proto_msgTypes[117] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -6738,7 +6951,7 @@ func (x *QueryRequest_OutputSerialization_JSONOutput) String() string { func (*QueryRequest_OutputSerialization_JSONOutput) ProtoMessage() {} func (x *QueryRequest_OutputSerialization_JSONOutput) ProtoReflect() protoreflect.Message { - mi := &file_volume_server_proto_msgTypes[115] + mi := &file_volume_server_proto_msgTypes[117] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -6751,7 +6964,7 @@ func (x *QueryRequest_OutputSerialization_JSONOutput) ProtoReflect() protoreflec // Deprecated: Use QueryRequest_OutputSerialization_JSONOutput.ProtoReflect.Descriptor instead. func (*QueryRequest_OutputSerialization_JSONOutput) Descriptor() ([]byte, []int) { - return file_volume_server_proto_rawDescGZIP(), []int{101, 2, 1} + return file_volume_server_proto_rawDescGZIP(), []int{103, 2, 1} } func (x *QueryRequest_OutputSerialization_JSONOutput) GetRecordDelimiter() string { @@ -6986,14 +7199,15 @@ const file_volume_server_proto_rawDesc = "" + "\n" + "collection\x18\x02 \x01(\tR\n" + "collection\" \n" + - "\x1eVolumeEcShardsGenerateResponse\"[\n" + + "\x1eVolumeEcShardsGenerateResponse\"\x8f\x01\n" + "\x1cVolumeEcShardsRebuildRequest\x12\x1b\n" + "\tvolume_id\x18\x01 \x01(\rR\bvolumeId\x12\x1e\n" + "\n" + "collection\x18\x02 \x01(\tR\n" + - "collection\"K\n" + + "collection\x122\n" + + "\x15unsafe_ignore_sidecar\x18\x03 \x01(\bR\x13unsafeIgnoreSidecar\"K\n" + "\x1dVolumeEcShardsRebuildResponse\x12*\n" + - "\x11rebuilt_shard_ids\x18\x01 \x03(\rR\x0frebuiltShardIds\"\xa4\x02\n" + + "\x11rebuilt_shard_ids\x18\x01 \x03(\rR\x0frebuiltShardIds\"\xcc\x02\n" + "\x19VolumeEcShardsCopyRequest\x12\x1b\n" + "\tvolume_id\x18\x01 \x01(\rR\bvolumeId\x12\x1e\n" + "\n" + @@ -7004,7 +7218,8 @@ const file_volume_server_proto_rawDesc = "" + "\x10source_data_node\x18\x05 \x01(\tR\x0esourceDataNode\x12\"\n" + "\rcopy_ecj_file\x18\x06 \x01(\bR\vcopyEcjFile\x12\"\n" + "\rcopy_vif_file\x18\a \x01(\bR\vcopyVifFile\x12\x17\n" + - "\adisk_id\x18\b \x01(\rR\x06diskId\"\x1c\n" + + "\adisk_id\x18\b \x01(\rR\x06diskId\x12&\n" + + "\x0fcopy_ecsum_file\x18\t \x01(\bR\rcopyEcsumFile\"\x1c\n" + "\x1aVolumeEcShardsCopyResponse\"w\n" + "\x1bVolumeEcShardsDeleteRequest\x12\x1b\n" + "\tvolume_id\x18\x01 \x01(\rR\bvolumeId\x12\x1e\n" + @@ -7126,7 +7341,22 @@ const file_volume_server_proto_rawDesc = "" + "\rEcShardConfig\x12\x1f\n" + "\vdata_shards\x18\x01 \x01(\rR\n" + "dataShards\x12#\n" + - "\rparity_shards\x18\x02 \x01(\rR\fparityShards\"\x8b\x02\n" + + "\rparity_shards\x18\x02 \x01(\rR\fparityShards\"\xbc\x02\n" + + "\x12EcBitrotProtection\x12A\n" + + "\talgorithm\x18\x01 \x01(\x0e2#.volume_server_pb.ChecksumAlgorithmR\talgorithm\x12\x1d\n" + + "\n" + + "block_size\x18\x02 \x01(\rR\tblockSize\x12\x1e\n" + + "\n" + + "generation\x18\x03 \x01(\rR\n" + + "generation\x12G\n" + + "\x0fec_shard_config\x18\x04 \x01(\v2\x1f.volume_server_pb.EcShardConfigR\recShardConfig\x12:\n" + + "\x06shards\x18\x05 \x03(\v2\".volume_server_pb.EcShardChecksumsR\x06shards\x12\x1f\n" + + "\vencode_uuid\x18\x06 \x01(\fR\n" + + "encodeUuid\"s\n" + + "\x10EcShardChecksums\x12\x19\n" + + "\bshard_id\x18\x01 \x01(\rR\ashardId\x12!\n" + + "\fcovered_size\x18\x02 \x01(\x03R\vcoveredSize\x12!\n" + + "\fblock_crc32c\x18\x03 \x01(\fR\vblockCrc32c\"\x8b\x02\n" + "\x14OldVersionVolumeInfo\x122\n" + "\x05files\x18\x01 \x03(\v2\x1c.volume_server_pb.RemoteFileR\x05files\x12\x18\n" + "\aversion\x18\x02 \x01(\rR\aversion\x12 \n" + @@ -7269,12 +7499,16 @@ const file_volume_server_proto_rawDesc = "" + "\rstart_time_ns\x18\x01 \x01(\x03R\vstartTimeNs\x12$\n" + "\x0eremote_time_ns\x18\x02 \x01(\x03R\fremoteTimeNs\x12 \n" + "\fstop_time_ns\x18\x03 \x01(\x03R\n" + - "stopTimeNs*>\n" + + "stopTimeNs*;\n" + + "\x11ChecksumAlgorithm\x12\x11\n" + + "\rCHECKSUM_NONE\x10\x00\x12\x13\n" + + "\x0fCHECKSUM_CRC32C\x10\x01*L\n" + "\x0fVolumeScrubMode\x12\v\n" + "\aUNKNOWN\x10\x00\x12\t\n" + "\x05INDEX\x10\x01\x12\b\n" + "\x04FULL\x10\x02\x12\t\n" + - "\x05LOCAL\x10\x032\xfb(\n" + + "\x05LOCAL\x10\x03\x12\f\n" + + "\bCHECKSUM\x10\x042\xfb(\n" + "\fVolumeServer\x12\\\n" + "\vBatchDelete\x12$.volume_server_pb.BatchDeleteRequest\x1a%.volume_server_pb.BatchDeleteResponse\"\x00\x12n\n" + "\x11VacuumVolumeCheck\x12*.volume_server_pb.VacuumVolumeCheckRequest\x1a+.volume_server_pb.VacuumVolumeCheckResponse\"\x00\x12v\n" + @@ -7338,258 +7572,264 @@ func file_volume_server_proto_rawDescGZIP() []byte { return file_volume_server_proto_rawDescData } -var file_volume_server_proto_enumTypes = make([]protoimpl.EnumInfo, 1) -var file_volume_server_proto_msgTypes = make([]protoimpl.MessageInfo, 116) +var file_volume_server_proto_enumTypes = make([]protoimpl.EnumInfo, 2) +var file_volume_server_proto_msgTypes = make([]protoimpl.MessageInfo, 118) var file_volume_server_proto_goTypes = []any{ - (VolumeScrubMode)(0), // 0: volume_server_pb.VolumeScrubMode - (*VolumeServerState)(nil), // 1: volume_server_pb.VolumeServerState - (*BatchDeleteRequest)(nil), // 2: volume_server_pb.BatchDeleteRequest - (*BatchDeleteResponse)(nil), // 3: volume_server_pb.BatchDeleteResponse - (*DeleteResult)(nil), // 4: volume_server_pb.DeleteResult - (*Empty)(nil), // 5: volume_server_pb.Empty - (*VacuumVolumeCheckRequest)(nil), // 6: volume_server_pb.VacuumVolumeCheckRequest - (*VacuumVolumeCheckResponse)(nil), // 7: volume_server_pb.VacuumVolumeCheckResponse - (*VacuumVolumeCompactRequest)(nil), // 8: volume_server_pb.VacuumVolumeCompactRequest - (*VacuumVolumeCompactResponse)(nil), // 9: volume_server_pb.VacuumVolumeCompactResponse - (*VacuumVolumeCommitRequest)(nil), // 10: volume_server_pb.VacuumVolumeCommitRequest - (*VacuumVolumeCommitResponse)(nil), // 11: volume_server_pb.VacuumVolumeCommitResponse - (*VacuumVolumeCleanupRequest)(nil), // 12: volume_server_pb.VacuumVolumeCleanupRequest - (*VacuumVolumeCleanupResponse)(nil), // 13: volume_server_pb.VacuumVolumeCleanupResponse - (*DeleteCollectionRequest)(nil), // 14: volume_server_pb.DeleteCollectionRequest - (*DeleteCollectionResponse)(nil), // 15: volume_server_pb.DeleteCollectionResponse - (*AllocateVolumeRequest)(nil), // 16: volume_server_pb.AllocateVolumeRequest - (*AllocateVolumeResponse)(nil), // 17: volume_server_pb.AllocateVolumeResponse - (*VolumeSyncStatusRequest)(nil), // 18: volume_server_pb.VolumeSyncStatusRequest - (*VolumeSyncStatusResponse)(nil), // 19: volume_server_pb.VolumeSyncStatusResponse - (*VolumeIncrementalCopyRequest)(nil), // 20: volume_server_pb.VolumeIncrementalCopyRequest - (*VolumeIncrementalCopyResponse)(nil), // 21: volume_server_pb.VolumeIncrementalCopyResponse - (*VolumeMountRequest)(nil), // 22: volume_server_pb.VolumeMountRequest - (*VolumeMountResponse)(nil), // 23: volume_server_pb.VolumeMountResponse - (*VolumeUnmountRequest)(nil), // 24: volume_server_pb.VolumeUnmountRequest - (*VolumeUnmountResponse)(nil), // 25: volume_server_pb.VolumeUnmountResponse - (*VolumeDeleteRequest)(nil), // 26: volume_server_pb.VolumeDeleteRequest - (*VolumeDeleteResponse)(nil), // 27: volume_server_pb.VolumeDeleteResponse - (*VolumeMarkReadonlyRequest)(nil), // 28: volume_server_pb.VolumeMarkReadonlyRequest - (*VolumeMarkReadonlyResponse)(nil), // 29: volume_server_pb.VolumeMarkReadonlyResponse - (*VolumeMarkWritableRequest)(nil), // 30: volume_server_pb.VolumeMarkWritableRequest - (*VolumeMarkWritableResponse)(nil), // 31: volume_server_pb.VolumeMarkWritableResponse - (*VolumeConfigureRequest)(nil), // 32: volume_server_pb.VolumeConfigureRequest - (*VolumeConfigureResponse)(nil), // 33: volume_server_pb.VolumeConfigureResponse - (*VolumeStatusRequest)(nil), // 34: volume_server_pb.VolumeStatusRequest - (*VolumeStatusResponse)(nil), // 35: volume_server_pb.VolumeStatusResponse - (*GetStateRequest)(nil), // 36: volume_server_pb.GetStateRequest - (*GetStateResponse)(nil), // 37: volume_server_pb.GetStateResponse - (*SetStateRequest)(nil), // 38: volume_server_pb.SetStateRequest - (*SetStateResponse)(nil), // 39: volume_server_pb.SetStateResponse - (*VolumeCopyRequest)(nil), // 40: volume_server_pb.VolumeCopyRequest - (*VolumeCopyResponse)(nil), // 41: volume_server_pb.VolumeCopyResponse - (*CopyFileRequest)(nil), // 42: volume_server_pb.CopyFileRequest - (*CopyFileResponse)(nil), // 43: volume_server_pb.CopyFileResponse - (*ReceiveFileRequest)(nil), // 44: volume_server_pb.ReceiveFileRequest - (*ReceiveFileInfo)(nil), // 45: volume_server_pb.ReceiveFileInfo - (*ReceiveFileResponse)(nil), // 46: volume_server_pb.ReceiveFileResponse - (*ReadNeedleBlobRequest)(nil), // 47: volume_server_pb.ReadNeedleBlobRequest - (*ReadNeedleBlobResponse)(nil), // 48: volume_server_pb.ReadNeedleBlobResponse - (*ReadNeedleMetaRequest)(nil), // 49: volume_server_pb.ReadNeedleMetaRequest - (*ReadNeedleMetaResponse)(nil), // 50: volume_server_pb.ReadNeedleMetaResponse - (*WriteNeedleBlobRequest)(nil), // 51: volume_server_pb.WriteNeedleBlobRequest - (*WriteNeedleBlobResponse)(nil), // 52: volume_server_pb.WriteNeedleBlobResponse - (*ReadAllNeedlesRequest)(nil), // 53: volume_server_pb.ReadAllNeedlesRequest - (*ReadAllNeedlesResponse)(nil), // 54: volume_server_pb.ReadAllNeedlesResponse - (*VolumeTailSenderRequest)(nil), // 55: volume_server_pb.VolumeTailSenderRequest - (*VolumeTailSenderResponse)(nil), // 56: volume_server_pb.VolumeTailSenderResponse - (*VolumeTailReceiverRequest)(nil), // 57: volume_server_pb.VolumeTailReceiverRequest - (*VolumeTailReceiverResponse)(nil), // 58: volume_server_pb.VolumeTailReceiverResponse - (*VolumeEcShardsGenerateRequest)(nil), // 59: volume_server_pb.VolumeEcShardsGenerateRequest - (*VolumeEcShardsGenerateResponse)(nil), // 60: volume_server_pb.VolumeEcShardsGenerateResponse - (*VolumeEcShardsRebuildRequest)(nil), // 61: volume_server_pb.VolumeEcShardsRebuildRequest - (*VolumeEcShardsRebuildResponse)(nil), // 62: volume_server_pb.VolumeEcShardsRebuildResponse - (*VolumeEcShardsCopyRequest)(nil), // 63: volume_server_pb.VolumeEcShardsCopyRequest - (*VolumeEcShardsCopyResponse)(nil), // 64: volume_server_pb.VolumeEcShardsCopyResponse - (*VolumeEcShardsDeleteRequest)(nil), // 65: volume_server_pb.VolumeEcShardsDeleteRequest - (*VolumeEcShardsDeleteResponse)(nil), // 66: volume_server_pb.VolumeEcShardsDeleteResponse - (*VolumeEcShardsMountRequest)(nil), // 67: volume_server_pb.VolumeEcShardsMountRequest - (*VolumeEcShardsMountResponse)(nil), // 68: volume_server_pb.VolumeEcShardsMountResponse - (*VolumeEcShardsUnmountRequest)(nil), // 69: volume_server_pb.VolumeEcShardsUnmountRequest - (*VolumeEcShardsUnmountResponse)(nil), // 70: volume_server_pb.VolumeEcShardsUnmountResponse - (*VolumeEcShardReadRequest)(nil), // 71: volume_server_pb.VolumeEcShardReadRequest - (*VolumeEcShardReadResponse)(nil), // 72: volume_server_pb.VolumeEcShardReadResponse - (*VolumeEcBlobDeleteRequest)(nil), // 73: volume_server_pb.VolumeEcBlobDeleteRequest - (*VolumeEcBlobDeleteResponse)(nil), // 74: volume_server_pb.VolumeEcBlobDeleteResponse - (*VolumeEcShardsToVolumeRequest)(nil), // 75: volume_server_pb.VolumeEcShardsToVolumeRequest - (*VolumeEcShardsToVolumeResponse)(nil), // 76: volume_server_pb.VolumeEcShardsToVolumeResponse - (*VolumeEcShardsInfoRequest)(nil), // 77: volume_server_pb.VolumeEcShardsInfoRequest - (*VolumeEcShardsInfoResponse)(nil), // 78: volume_server_pb.VolumeEcShardsInfoResponse - (*EcShardInfo)(nil), // 79: volume_server_pb.EcShardInfo - (*ReadVolumeFileStatusRequest)(nil), // 80: volume_server_pb.ReadVolumeFileStatusRequest - (*ReadVolumeFileStatusResponse)(nil), // 81: volume_server_pb.ReadVolumeFileStatusResponse - (*DiskStatus)(nil), // 82: volume_server_pb.DiskStatus - (*MemStatus)(nil), // 83: volume_server_pb.MemStatus - (*RemoteFile)(nil), // 84: volume_server_pb.RemoteFile - (*VolumeInfo)(nil), // 85: volume_server_pb.VolumeInfo - (*EcShardConfig)(nil), // 86: volume_server_pb.EcShardConfig - (*OldVersionVolumeInfo)(nil), // 87: volume_server_pb.OldVersionVolumeInfo - (*VolumeTierMoveDatToRemoteRequest)(nil), // 88: volume_server_pb.VolumeTierMoveDatToRemoteRequest - (*VolumeTierMoveDatToRemoteResponse)(nil), // 89: volume_server_pb.VolumeTierMoveDatToRemoteResponse - (*VolumeTierMoveDatFromRemoteRequest)(nil), // 90: volume_server_pb.VolumeTierMoveDatFromRemoteRequest - (*VolumeTierMoveDatFromRemoteResponse)(nil), // 91: volume_server_pb.VolumeTierMoveDatFromRemoteResponse - (*VolumeServerStatusRequest)(nil), // 92: volume_server_pb.VolumeServerStatusRequest - (*VolumeServerStatusResponse)(nil), // 93: volume_server_pb.VolumeServerStatusResponse - (*VolumeServerLeaveRequest)(nil), // 94: volume_server_pb.VolumeServerLeaveRequest - (*VolumeServerLeaveResponse)(nil), // 95: volume_server_pb.VolumeServerLeaveResponse - (*FetchAndWriteNeedleRequest)(nil), // 96: volume_server_pb.FetchAndWriteNeedleRequest - (*FetchAndWriteNeedleResponse)(nil), // 97: volume_server_pb.FetchAndWriteNeedleResponse - (*ScrubVolumeRequest)(nil), // 98: volume_server_pb.ScrubVolumeRequest - (*ScrubVolumeResponse)(nil), // 99: volume_server_pb.ScrubVolumeResponse - (*ScrubEcVolumeRequest)(nil), // 100: volume_server_pb.ScrubEcVolumeRequest - (*ScrubEcVolumeResponse)(nil), // 101: volume_server_pb.ScrubEcVolumeResponse - (*QueryRequest)(nil), // 102: volume_server_pb.QueryRequest - (*QueriedStripe)(nil), // 103: volume_server_pb.QueriedStripe - (*VolumeNeedleStatusRequest)(nil), // 104: volume_server_pb.VolumeNeedleStatusRequest - (*VolumeNeedleStatusResponse)(nil), // 105: volume_server_pb.VolumeNeedleStatusResponse - (*PingRequest)(nil), // 106: volume_server_pb.PingRequest - (*PingResponse)(nil), // 107: volume_server_pb.PingResponse - (*FetchAndWriteNeedleRequest_Replica)(nil), // 108: volume_server_pb.FetchAndWriteNeedleRequest.Replica - (*QueryRequest_Filter)(nil), // 109: volume_server_pb.QueryRequest.Filter - (*QueryRequest_InputSerialization)(nil), // 110: volume_server_pb.QueryRequest.InputSerialization - (*QueryRequest_OutputSerialization)(nil), // 111: volume_server_pb.QueryRequest.OutputSerialization - (*QueryRequest_InputSerialization_CSVInput)(nil), // 112: volume_server_pb.QueryRequest.InputSerialization.CSVInput - (*QueryRequest_InputSerialization_JSONInput)(nil), // 113: volume_server_pb.QueryRequest.InputSerialization.JSONInput - (*QueryRequest_InputSerialization_ParquetInput)(nil), // 114: volume_server_pb.QueryRequest.InputSerialization.ParquetInput - (*QueryRequest_OutputSerialization_CSVOutput)(nil), // 115: volume_server_pb.QueryRequest.OutputSerialization.CSVOutput - (*QueryRequest_OutputSerialization_JSONOutput)(nil), // 116: volume_server_pb.QueryRequest.OutputSerialization.JSONOutput - (*remote_pb.RemoteConf)(nil), // 117: remote_pb.RemoteConf - (*remote_pb.RemoteStorageLocation)(nil), // 118: remote_pb.RemoteStorageLocation + (ChecksumAlgorithm)(0), // 0: volume_server_pb.ChecksumAlgorithm + (VolumeScrubMode)(0), // 1: volume_server_pb.VolumeScrubMode + (*VolumeServerState)(nil), // 2: volume_server_pb.VolumeServerState + (*BatchDeleteRequest)(nil), // 3: volume_server_pb.BatchDeleteRequest + (*BatchDeleteResponse)(nil), // 4: volume_server_pb.BatchDeleteResponse + (*DeleteResult)(nil), // 5: volume_server_pb.DeleteResult + (*Empty)(nil), // 6: volume_server_pb.Empty + (*VacuumVolumeCheckRequest)(nil), // 7: volume_server_pb.VacuumVolumeCheckRequest + (*VacuumVolumeCheckResponse)(nil), // 8: volume_server_pb.VacuumVolumeCheckResponse + (*VacuumVolumeCompactRequest)(nil), // 9: volume_server_pb.VacuumVolumeCompactRequest + (*VacuumVolumeCompactResponse)(nil), // 10: volume_server_pb.VacuumVolumeCompactResponse + (*VacuumVolumeCommitRequest)(nil), // 11: volume_server_pb.VacuumVolumeCommitRequest + (*VacuumVolumeCommitResponse)(nil), // 12: volume_server_pb.VacuumVolumeCommitResponse + (*VacuumVolumeCleanupRequest)(nil), // 13: volume_server_pb.VacuumVolumeCleanupRequest + (*VacuumVolumeCleanupResponse)(nil), // 14: volume_server_pb.VacuumVolumeCleanupResponse + (*DeleteCollectionRequest)(nil), // 15: volume_server_pb.DeleteCollectionRequest + (*DeleteCollectionResponse)(nil), // 16: volume_server_pb.DeleteCollectionResponse + (*AllocateVolumeRequest)(nil), // 17: volume_server_pb.AllocateVolumeRequest + (*AllocateVolumeResponse)(nil), // 18: volume_server_pb.AllocateVolumeResponse + (*VolumeSyncStatusRequest)(nil), // 19: volume_server_pb.VolumeSyncStatusRequest + (*VolumeSyncStatusResponse)(nil), // 20: volume_server_pb.VolumeSyncStatusResponse + (*VolumeIncrementalCopyRequest)(nil), // 21: volume_server_pb.VolumeIncrementalCopyRequest + (*VolumeIncrementalCopyResponse)(nil), // 22: volume_server_pb.VolumeIncrementalCopyResponse + (*VolumeMountRequest)(nil), // 23: volume_server_pb.VolumeMountRequest + (*VolumeMountResponse)(nil), // 24: volume_server_pb.VolumeMountResponse + (*VolumeUnmountRequest)(nil), // 25: volume_server_pb.VolumeUnmountRequest + (*VolumeUnmountResponse)(nil), // 26: volume_server_pb.VolumeUnmountResponse + (*VolumeDeleteRequest)(nil), // 27: volume_server_pb.VolumeDeleteRequest + (*VolumeDeleteResponse)(nil), // 28: volume_server_pb.VolumeDeleteResponse + (*VolumeMarkReadonlyRequest)(nil), // 29: volume_server_pb.VolumeMarkReadonlyRequest + (*VolumeMarkReadonlyResponse)(nil), // 30: volume_server_pb.VolumeMarkReadonlyResponse + (*VolumeMarkWritableRequest)(nil), // 31: volume_server_pb.VolumeMarkWritableRequest + (*VolumeMarkWritableResponse)(nil), // 32: volume_server_pb.VolumeMarkWritableResponse + (*VolumeConfigureRequest)(nil), // 33: volume_server_pb.VolumeConfigureRequest + (*VolumeConfigureResponse)(nil), // 34: volume_server_pb.VolumeConfigureResponse + (*VolumeStatusRequest)(nil), // 35: volume_server_pb.VolumeStatusRequest + (*VolumeStatusResponse)(nil), // 36: volume_server_pb.VolumeStatusResponse + (*GetStateRequest)(nil), // 37: volume_server_pb.GetStateRequest + (*GetStateResponse)(nil), // 38: volume_server_pb.GetStateResponse + (*SetStateRequest)(nil), // 39: volume_server_pb.SetStateRequest + (*SetStateResponse)(nil), // 40: volume_server_pb.SetStateResponse + (*VolumeCopyRequest)(nil), // 41: volume_server_pb.VolumeCopyRequest + (*VolumeCopyResponse)(nil), // 42: volume_server_pb.VolumeCopyResponse + (*CopyFileRequest)(nil), // 43: volume_server_pb.CopyFileRequest + (*CopyFileResponse)(nil), // 44: volume_server_pb.CopyFileResponse + (*ReceiveFileRequest)(nil), // 45: volume_server_pb.ReceiveFileRequest + (*ReceiveFileInfo)(nil), // 46: volume_server_pb.ReceiveFileInfo + (*ReceiveFileResponse)(nil), // 47: volume_server_pb.ReceiveFileResponse + (*ReadNeedleBlobRequest)(nil), // 48: volume_server_pb.ReadNeedleBlobRequest + (*ReadNeedleBlobResponse)(nil), // 49: volume_server_pb.ReadNeedleBlobResponse + (*ReadNeedleMetaRequest)(nil), // 50: volume_server_pb.ReadNeedleMetaRequest + (*ReadNeedleMetaResponse)(nil), // 51: volume_server_pb.ReadNeedleMetaResponse + (*WriteNeedleBlobRequest)(nil), // 52: volume_server_pb.WriteNeedleBlobRequest + (*WriteNeedleBlobResponse)(nil), // 53: volume_server_pb.WriteNeedleBlobResponse + (*ReadAllNeedlesRequest)(nil), // 54: volume_server_pb.ReadAllNeedlesRequest + (*ReadAllNeedlesResponse)(nil), // 55: volume_server_pb.ReadAllNeedlesResponse + (*VolumeTailSenderRequest)(nil), // 56: volume_server_pb.VolumeTailSenderRequest + (*VolumeTailSenderResponse)(nil), // 57: volume_server_pb.VolumeTailSenderResponse + (*VolumeTailReceiverRequest)(nil), // 58: volume_server_pb.VolumeTailReceiverRequest + (*VolumeTailReceiverResponse)(nil), // 59: volume_server_pb.VolumeTailReceiverResponse + (*VolumeEcShardsGenerateRequest)(nil), // 60: volume_server_pb.VolumeEcShardsGenerateRequest + (*VolumeEcShardsGenerateResponse)(nil), // 61: volume_server_pb.VolumeEcShardsGenerateResponse + (*VolumeEcShardsRebuildRequest)(nil), // 62: volume_server_pb.VolumeEcShardsRebuildRequest + (*VolumeEcShardsRebuildResponse)(nil), // 63: volume_server_pb.VolumeEcShardsRebuildResponse + (*VolumeEcShardsCopyRequest)(nil), // 64: volume_server_pb.VolumeEcShardsCopyRequest + (*VolumeEcShardsCopyResponse)(nil), // 65: volume_server_pb.VolumeEcShardsCopyResponse + (*VolumeEcShardsDeleteRequest)(nil), // 66: volume_server_pb.VolumeEcShardsDeleteRequest + (*VolumeEcShardsDeleteResponse)(nil), // 67: volume_server_pb.VolumeEcShardsDeleteResponse + (*VolumeEcShardsMountRequest)(nil), // 68: volume_server_pb.VolumeEcShardsMountRequest + (*VolumeEcShardsMountResponse)(nil), // 69: volume_server_pb.VolumeEcShardsMountResponse + (*VolumeEcShardsUnmountRequest)(nil), // 70: volume_server_pb.VolumeEcShardsUnmountRequest + (*VolumeEcShardsUnmountResponse)(nil), // 71: volume_server_pb.VolumeEcShardsUnmountResponse + (*VolumeEcShardReadRequest)(nil), // 72: volume_server_pb.VolumeEcShardReadRequest + (*VolumeEcShardReadResponse)(nil), // 73: volume_server_pb.VolumeEcShardReadResponse + (*VolumeEcBlobDeleteRequest)(nil), // 74: volume_server_pb.VolumeEcBlobDeleteRequest + (*VolumeEcBlobDeleteResponse)(nil), // 75: volume_server_pb.VolumeEcBlobDeleteResponse + (*VolumeEcShardsToVolumeRequest)(nil), // 76: volume_server_pb.VolumeEcShardsToVolumeRequest + (*VolumeEcShardsToVolumeResponse)(nil), // 77: volume_server_pb.VolumeEcShardsToVolumeResponse + (*VolumeEcShardsInfoRequest)(nil), // 78: volume_server_pb.VolumeEcShardsInfoRequest + (*VolumeEcShardsInfoResponse)(nil), // 79: volume_server_pb.VolumeEcShardsInfoResponse + (*EcShardInfo)(nil), // 80: volume_server_pb.EcShardInfo + (*ReadVolumeFileStatusRequest)(nil), // 81: volume_server_pb.ReadVolumeFileStatusRequest + (*ReadVolumeFileStatusResponse)(nil), // 82: volume_server_pb.ReadVolumeFileStatusResponse + (*DiskStatus)(nil), // 83: volume_server_pb.DiskStatus + (*MemStatus)(nil), // 84: volume_server_pb.MemStatus + (*RemoteFile)(nil), // 85: volume_server_pb.RemoteFile + (*VolumeInfo)(nil), // 86: volume_server_pb.VolumeInfo + (*EcShardConfig)(nil), // 87: volume_server_pb.EcShardConfig + (*EcBitrotProtection)(nil), // 88: volume_server_pb.EcBitrotProtection + (*EcShardChecksums)(nil), // 89: volume_server_pb.EcShardChecksums + (*OldVersionVolumeInfo)(nil), // 90: volume_server_pb.OldVersionVolumeInfo + (*VolumeTierMoveDatToRemoteRequest)(nil), // 91: volume_server_pb.VolumeTierMoveDatToRemoteRequest + (*VolumeTierMoveDatToRemoteResponse)(nil), // 92: volume_server_pb.VolumeTierMoveDatToRemoteResponse + (*VolumeTierMoveDatFromRemoteRequest)(nil), // 93: volume_server_pb.VolumeTierMoveDatFromRemoteRequest + (*VolumeTierMoveDatFromRemoteResponse)(nil), // 94: volume_server_pb.VolumeTierMoveDatFromRemoteResponse + (*VolumeServerStatusRequest)(nil), // 95: volume_server_pb.VolumeServerStatusRequest + (*VolumeServerStatusResponse)(nil), // 96: volume_server_pb.VolumeServerStatusResponse + (*VolumeServerLeaveRequest)(nil), // 97: volume_server_pb.VolumeServerLeaveRequest + (*VolumeServerLeaveResponse)(nil), // 98: volume_server_pb.VolumeServerLeaveResponse + (*FetchAndWriteNeedleRequest)(nil), // 99: volume_server_pb.FetchAndWriteNeedleRequest + (*FetchAndWriteNeedleResponse)(nil), // 100: volume_server_pb.FetchAndWriteNeedleResponse + (*ScrubVolumeRequest)(nil), // 101: volume_server_pb.ScrubVolumeRequest + (*ScrubVolumeResponse)(nil), // 102: volume_server_pb.ScrubVolumeResponse + (*ScrubEcVolumeRequest)(nil), // 103: volume_server_pb.ScrubEcVolumeRequest + (*ScrubEcVolumeResponse)(nil), // 104: volume_server_pb.ScrubEcVolumeResponse + (*QueryRequest)(nil), // 105: volume_server_pb.QueryRequest + (*QueriedStripe)(nil), // 106: volume_server_pb.QueriedStripe + (*VolumeNeedleStatusRequest)(nil), // 107: volume_server_pb.VolumeNeedleStatusRequest + (*VolumeNeedleStatusResponse)(nil), // 108: volume_server_pb.VolumeNeedleStatusResponse + (*PingRequest)(nil), // 109: volume_server_pb.PingRequest + (*PingResponse)(nil), // 110: volume_server_pb.PingResponse + (*FetchAndWriteNeedleRequest_Replica)(nil), // 111: volume_server_pb.FetchAndWriteNeedleRequest.Replica + (*QueryRequest_Filter)(nil), // 112: volume_server_pb.QueryRequest.Filter + (*QueryRequest_InputSerialization)(nil), // 113: volume_server_pb.QueryRequest.InputSerialization + (*QueryRequest_OutputSerialization)(nil), // 114: volume_server_pb.QueryRequest.OutputSerialization + (*QueryRequest_InputSerialization_CSVInput)(nil), // 115: volume_server_pb.QueryRequest.InputSerialization.CSVInput + (*QueryRequest_InputSerialization_JSONInput)(nil), // 116: volume_server_pb.QueryRequest.InputSerialization.JSONInput + (*QueryRequest_InputSerialization_ParquetInput)(nil), // 117: volume_server_pb.QueryRequest.InputSerialization.ParquetInput + (*QueryRequest_OutputSerialization_CSVOutput)(nil), // 118: volume_server_pb.QueryRequest.OutputSerialization.CSVOutput + (*QueryRequest_OutputSerialization_JSONOutput)(nil), // 119: volume_server_pb.QueryRequest.OutputSerialization.JSONOutput + (*remote_pb.RemoteConf)(nil), // 120: remote_pb.RemoteConf + (*remote_pb.RemoteStorageLocation)(nil), // 121: remote_pb.RemoteStorageLocation } var file_volume_server_proto_depIdxs = []int32{ - 4, // 0: volume_server_pb.BatchDeleteResponse.results:type_name -> volume_server_pb.DeleteResult - 1, // 1: volume_server_pb.GetStateResponse.state:type_name -> volume_server_pb.VolumeServerState - 1, // 2: volume_server_pb.SetStateRequest.state:type_name -> volume_server_pb.VolumeServerState - 1, // 3: volume_server_pb.SetStateResponse.state:type_name -> volume_server_pb.VolumeServerState - 45, // 4: volume_server_pb.ReceiveFileRequest.info:type_name -> volume_server_pb.ReceiveFileInfo - 79, // 5: volume_server_pb.VolumeEcShardsInfoResponse.ec_shard_infos:type_name -> volume_server_pb.EcShardInfo - 85, // 6: volume_server_pb.ReadVolumeFileStatusResponse.volume_info:type_name -> volume_server_pb.VolumeInfo - 84, // 7: volume_server_pb.VolumeInfo.files:type_name -> volume_server_pb.RemoteFile - 86, // 8: volume_server_pb.VolumeInfo.ec_shard_config:type_name -> volume_server_pb.EcShardConfig - 84, // 9: volume_server_pb.OldVersionVolumeInfo.files:type_name -> volume_server_pb.RemoteFile - 82, // 10: volume_server_pb.VolumeServerStatusResponse.disk_statuses:type_name -> volume_server_pb.DiskStatus - 83, // 11: volume_server_pb.VolumeServerStatusResponse.memory_status:type_name -> volume_server_pb.MemStatus - 1, // 12: volume_server_pb.VolumeServerStatusResponse.state:type_name -> volume_server_pb.VolumeServerState - 108, // 13: volume_server_pb.FetchAndWriteNeedleRequest.replicas:type_name -> volume_server_pb.FetchAndWriteNeedleRequest.Replica - 117, // 14: volume_server_pb.FetchAndWriteNeedleRequest.remote_conf:type_name -> remote_pb.RemoteConf - 118, // 15: volume_server_pb.FetchAndWriteNeedleRequest.remote_location:type_name -> remote_pb.RemoteStorageLocation - 0, // 16: volume_server_pb.ScrubVolumeRequest.mode:type_name -> volume_server_pb.VolumeScrubMode - 0, // 17: volume_server_pb.ScrubEcVolumeRequest.mode:type_name -> volume_server_pb.VolumeScrubMode - 79, // 18: volume_server_pb.ScrubEcVolumeResponse.broken_shard_infos:type_name -> volume_server_pb.EcShardInfo - 109, // 19: volume_server_pb.QueryRequest.filter:type_name -> volume_server_pb.QueryRequest.Filter - 110, // 20: volume_server_pb.QueryRequest.input_serialization:type_name -> volume_server_pb.QueryRequest.InputSerialization - 111, // 21: volume_server_pb.QueryRequest.output_serialization:type_name -> volume_server_pb.QueryRequest.OutputSerialization - 112, // 22: volume_server_pb.QueryRequest.InputSerialization.csv_input:type_name -> volume_server_pb.QueryRequest.InputSerialization.CSVInput - 113, // 23: volume_server_pb.QueryRequest.InputSerialization.json_input:type_name -> volume_server_pb.QueryRequest.InputSerialization.JSONInput - 114, // 24: volume_server_pb.QueryRequest.InputSerialization.parquet_input:type_name -> volume_server_pb.QueryRequest.InputSerialization.ParquetInput - 115, // 25: volume_server_pb.QueryRequest.OutputSerialization.csv_output:type_name -> volume_server_pb.QueryRequest.OutputSerialization.CSVOutput - 116, // 26: volume_server_pb.QueryRequest.OutputSerialization.json_output:type_name -> volume_server_pb.QueryRequest.OutputSerialization.JSONOutput - 2, // 27: volume_server_pb.VolumeServer.BatchDelete:input_type -> volume_server_pb.BatchDeleteRequest - 6, // 28: volume_server_pb.VolumeServer.VacuumVolumeCheck:input_type -> volume_server_pb.VacuumVolumeCheckRequest - 8, // 29: volume_server_pb.VolumeServer.VacuumVolumeCompact:input_type -> volume_server_pb.VacuumVolumeCompactRequest - 10, // 30: volume_server_pb.VolumeServer.VacuumVolumeCommit:input_type -> volume_server_pb.VacuumVolumeCommitRequest - 12, // 31: volume_server_pb.VolumeServer.VacuumVolumeCleanup:input_type -> volume_server_pb.VacuumVolumeCleanupRequest - 14, // 32: volume_server_pb.VolumeServer.DeleteCollection:input_type -> volume_server_pb.DeleteCollectionRequest - 16, // 33: volume_server_pb.VolumeServer.AllocateVolume:input_type -> volume_server_pb.AllocateVolumeRequest - 18, // 34: volume_server_pb.VolumeServer.VolumeSyncStatus:input_type -> volume_server_pb.VolumeSyncStatusRequest - 20, // 35: volume_server_pb.VolumeServer.VolumeIncrementalCopy:input_type -> volume_server_pb.VolumeIncrementalCopyRequest - 22, // 36: volume_server_pb.VolumeServer.VolumeMount:input_type -> volume_server_pb.VolumeMountRequest - 24, // 37: volume_server_pb.VolumeServer.VolumeUnmount:input_type -> volume_server_pb.VolumeUnmountRequest - 26, // 38: volume_server_pb.VolumeServer.VolumeDelete:input_type -> volume_server_pb.VolumeDeleteRequest - 28, // 39: volume_server_pb.VolumeServer.VolumeMarkReadonly:input_type -> volume_server_pb.VolumeMarkReadonlyRequest - 30, // 40: volume_server_pb.VolumeServer.VolumeMarkWritable:input_type -> volume_server_pb.VolumeMarkWritableRequest - 32, // 41: volume_server_pb.VolumeServer.VolumeConfigure:input_type -> volume_server_pb.VolumeConfigureRequest - 34, // 42: volume_server_pb.VolumeServer.VolumeStatus:input_type -> volume_server_pb.VolumeStatusRequest - 36, // 43: volume_server_pb.VolumeServer.GetState:input_type -> volume_server_pb.GetStateRequest - 38, // 44: volume_server_pb.VolumeServer.SetState:input_type -> volume_server_pb.SetStateRequest - 40, // 45: volume_server_pb.VolumeServer.VolumeCopy:input_type -> volume_server_pb.VolumeCopyRequest - 80, // 46: volume_server_pb.VolumeServer.ReadVolumeFileStatus:input_type -> volume_server_pb.ReadVolumeFileStatusRequest - 42, // 47: volume_server_pb.VolumeServer.CopyFile:input_type -> volume_server_pb.CopyFileRequest - 44, // 48: volume_server_pb.VolumeServer.ReceiveFile:input_type -> volume_server_pb.ReceiveFileRequest - 47, // 49: volume_server_pb.VolumeServer.ReadNeedleBlob:input_type -> volume_server_pb.ReadNeedleBlobRequest - 49, // 50: volume_server_pb.VolumeServer.ReadNeedleMeta:input_type -> volume_server_pb.ReadNeedleMetaRequest - 51, // 51: volume_server_pb.VolumeServer.WriteNeedleBlob:input_type -> volume_server_pb.WriteNeedleBlobRequest - 53, // 52: volume_server_pb.VolumeServer.ReadAllNeedles:input_type -> volume_server_pb.ReadAllNeedlesRequest - 55, // 53: volume_server_pb.VolumeServer.VolumeTailSender:input_type -> volume_server_pb.VolumeTailSenderRequest - 57, // 54: volume_server_pb.VolumeServer.VolumeTailReceiver:input_type -> volume_server_pb.VolumeTailReceiverRequest - 59, // 55: volume_server_pb.VolumeServer.VolumeEcShardsGenerate:input_type -> volume_server_pb.VolumeEcShardsGenerateRequest - 61, // 56: volume_server_pb.VolumeServer.VolumeEcShardsRebuild:input_type -> volume_server_pb.VolumeEcShardsRebuildRequest - 63, // 57: volume_server_pb.VolumeServer.VolumeEcShardsCopy:input_type -> volume_server_pb.VolumeEcShardsCopyRequest - 65, // 58: volume_server_pb.VolumeServer.VolumeEcShardsDelete:input_type -> volume_server_pb.VolumeEcShardsDeleteRequest - 67, // 59: volume_server_pb.VolumeServer.VolumeEcShardsMount:input_type -> volume_server_pb.VolumeEcShardsMountRequest - 69, // 60: volume_server_pb.VolumeServer.VolumeEcShardsUnmount:input_type -> volume_server_pb.VolumeEcShardsUnmountRequest - 71, // 61: volume_server_pb.VolumeServer.VolumeEcShardRead:input_type -> volume_server_pb.VolumeEcShardReadRequest - 73, // 62: volume_server_pb.VolumeServer.VolumeEcBlobDelete:input_type -> volume_server_pb.VolumeEcBlobDeleteRequest - 75, // 63: volume_server_pb.VolumeServer.VolumeEcShardsToVolume:input_type -> volume_server_pb.VolumeEcShardsToVolumeRequest - 77, // 64: volume_server_pb.VolumeServer.VolumeEcShardsInfo:input_type -> volume_server_pb.VolumeEcShardsInfoRequest - 88, // 65: volume_server_pb.VolumeServer.VolumeTierMoveDatToRemote:input_type -> volume_server_pb.VolumeTierMoveDatToRemoteRequest - 90, // 66: volume_server_pb.VolumeServer.VolumeTierMoveDatFromRemote:input_type -> volume_server_pb.VolumeTierMoveDatFromRemoteRequest - 92, // 67: volume_server_pb.VolumeServer.VolumeServerStatus:input_type -> volume_server_pb.VolumeServerStatusRequest - 94, // 68: volume_server_pb.VolumeServer.VolumeServerLeave:input_type -> volume_server_pb.VolumeServerLeaveRequest - 96, // 69: volume_server_pb.VolumeServer.FetchAndWriteNeedle:input_type -> volume_server_pb.FetchAndWriteNeedleRequest - 98, // 70: volume_server_pb.VolumeServer.ScrubVolume:input_type -> volume_server_pb.ScrubVolumeRequest - 100, // 71: volume_server_pb.VolumeServer.ScrubEcVolume:input_type -> volume_server_pb.ScrubEcVolumeRequest - 102, // 72: volume_server_pb.VolumeServer.Query:input_type -> volume_server_pb.QueryRequest - 104, // 73: volume_server_pb.VolumeServer.VolumeNeedleStatus:input_type -> volume_server_pb.VolumeNeedleStatusRequest - 106, // 74: volume_server_pb.VolumeServer.Ping:input_type -> volume_server_pb.PingRequest - 3, // 75: volume_server_pb.VolumeServer.BatchDelete:output_type -> volume_server_pb.BatchDeleteResponse - 7, // 76: volume_server_pb.VolumeServer.VacuumVolumeCheck:output_type -> volume_server_pb.VacuumVolumeCheckResponse - 9, // 77: volume_server_pb.VolumeServer.VacuumVolumeCompact:output_type -> volume_server_pb.VacuumVolumeCompactResponse - 11, // 78: volume_server_pb.VolumeServer.VacuumVolumeCommit:output_type -> volume_server_pb.VacuumVolumeCommitResponse - 13, // 79: volume_server_pb.VolumeServer.VacuumVolumeCleanup:output_type -> volume_server_pb.VacuumVolumeCleanupResponse - 15, // 80: volume_server_pb.VolumeServer.DeleteCollection:output_type -> volume_server_pb.DeleteCollectionResponse - 17, // 81: volume_server_pb.VolumeServer.AllocateVolume:output_type -> volume_server_pb.AllocateVolumeResponse - 19, // 82: volume_server_pb.VolumeServer.VolumeSyncStatus:output_type -> volume_server_pb.VolumeSyncStatusResponse - 21, // 83: volume_server_pb.VolumeServer.VolumeIncrementalCopy:output_type -> volume_server_pb.VolumeIncrementalCopyResponse - 23, // 84: volume_server_pb.VolumeServer.VolumeMount:output_type -> volume_server_pb.VolumeMountResponse - 25, // 85: volume_server_pb.VolumeServer.VolumeUnmount:output_type -> volume_server_pb.VolumeUnmountResponse - 27, // 86: volume_server_pb.VolumeServer.VolumeDelete:output_type -> volume_server_pb.VolumeDeleteResponse - 29, // 87: volume_server_pb.VolumeServer.VolumeMarkReadonly:output_type -> volume_server_pb.VolumeMarkReadonlyResponse - 31, // 88: volume_server_pb.VolumeServer.VolumeMarkWritable:output_type -> volume_server_pb.VolumeMarkWritableResponse - 33, // 89: volume_server_pb.VolumeServer.VolumeConfigure:output_type -> volume_server_pb.VolumeConfigureResponse - 35, // 90: volume_server_pb.VolumeServer.VolumeStatus:output_type -> volume_server_pb.VolumeStatusResponse - 37, // 91: volume_server_pb.VolumeServer.GetState:output_type -> volume_server_pb.GetStateResponse - 39, // 92: volume_server_pb.VolumeServer.SetState:output_type -> volume_server_pb.SetStateResponse - 41, // 93: volume_server_pb.VolumeServer.VolumeCopy:output_type -> volume_server_pb.VolumeCopyResponse - 81, // 94: volume_server_pb.VolumeServer.ReadVolumeFileStatus:output_type -> volume_server_pb.ReadVolumeFileStatusResponse - 43, // 95: volume_server_pb.VolumeServer.CopyFile:output_type -> volume_server_pb.CopyFileResponse - 46, // 96: volume_server_pb.VolumeServer.ReceiveFile:output_type -> volume_server_pb.ReceiveFileResponse - 48, // 97: volume_server_pb.VolumeServer.ReadNeedleBlob:output_type -> volume_server_pb.ReadNeedleBlobResponse - 50, // 98: volume_server_pb.VolumeServer.ReadNeedleMeta:output_type -> volume_server_pb.ReadNeedleMetaResponse - 52, // 99: volume_server_pb.VolumeServer.WriteNeedleBlob:output_type -> volume_server_pb.WriteNeedleBlobResponse - 54, // 100: volume_server_pb.VolumeServer.ReadAllNeedles:output_type -> volume_server_pb.ReadAllNeedlesResponse - 56, // 101: volume_server_pb.VolumeServer.VolumeTailSender:output_type -> volume_server_pb.VolumeTailSenderResponse - 58, // 102: volume_server_pb.VolumeServer.VolumeTailReceiver:output_type -> volume_server_pb.VolumeTailReceiverResponse - 60, // 103: volume_server_pb.VolumeServer.VolumeEcShardsGenerate:output_type -> volume_server_pb.VolumeEcShardsGenerateResponse - 62, // 104: volume_server_pb.VolumeServer.VolumeEcShardsRebuild:output_type -> volume_server_pb.VolumeEcShardsRebuildResponse - 64, // 105: volume_server_pb.VolumeServer.VolumeEcShardsCopy:output_type -> volume_server_pb.VolumeEcShardsCopyResponse - 66, // 106: volume_server_pb.VolumeServer.VolumeEcShardsDelete:output_type -> volume_server_pb.VolumeEcShardsDeleteResponse - 68, // 107: volume_server_pb.VolumeServer.VolumeEcShardsMount:output_type -> volume_server_pb.VolumeEcShardsMountResponse - 70, // 108: volume_server_pb.VolumeServer.VolumeEcShardsUnmount:output_type -> volume_server_pb.VolumeEcShardsUnmountResponse - 72, // 109: volume_server_pb.VolumeServer.VolumeEcShardRead:output_type -> volume_server_pb.VolumeEcShardReadResponse - 74, // 110: volume_server_pb.VolumeServer.VolumeEcBlobDelete:output_type -> volume_server_pb.VolumeEcBlobDeleteResponse - 76, // 111: volume_server_pb.VolumeServer.VolumeEcShardsToVolume:output_type -> volume_server_pb.VolumeEcShardsToVolumeResponse - 78, // 112: volume_server_pb.VolumeServer.VolumeEcShardsInfo:output_type -> volume_server_pb.VolumeEcShardsInfoResponse - 89, // 113: volume_server_pb.VolumeServer.VolumeTierMoveDatToRemote:output_type -> volume_server_pb.VolumeTierMoveDatToRemoteResponse - 91, // 114: volume_server_pb.VolumeServer.VolumeTierMoveDatFromRemote:output_type -> volume_server_pb.VolumeTierMoveDatFromRemoteResponse - 93, // 115: volume_server_pb.VolumeServer.VolumeServerStatus:output_type -> volume_server_pb.VolumeServerStatusResponse - 95, // 116: volume_server_pb.VolumeServer.VolumeServerLeave:output_type -> volume_server_pb.VolumeServerLeaveResponse - 97, // 117: volume_server_pb.VolumeServer.FetchAndWriteNeedle:output_type -> volume_server_pb.FetchAndWriteNeedleResponse - 99, // 118: volume_server_pb.VolumeServer.ScrubVolume:output_type -> volume_server_pb.ScrubVolumeResponse - 101, // 119: volume_server_pb.VolumeServer.ScrubEcVolume:output_type -> volume_server_pb.ScrubEcVolumeResponse - 103, // 120: volume_server_pb.VolumeServer.Query:output_type -> volume_server_pb.QueriedStripe - 105, // 121: volume_server_pb.VolumeServer.VolumeNeedleStatus:output_type -> volume_server_pb.VolumeNeedleStatusResponse - 107, // 122: volume_server_pb.VolumeServer.Ping:output_type -> volume_server_pb.PingResponse - 75, // [75:123] is the sub-list for method output_type - 27, // [27:75] is the sub-list for method input_type - 27, // [27:27] is the sub-list for extension type_name - 27, // [27:27] is the sub-list for extension extendee - 0, // [0:27] is the sub-list for field type_name + 5, // 0: volume_server_pb.BatchDeleteResponse.results:type_name -> volume_server_pb.DeleteResult + 2, // 1: volume_server_pb.GetStateResponse.state:type_name -> volume_server_pb.VolumeServerState + 2, // 2: volume_server_pb.SetStateRequest.state:type_name -> volume_server_pb.VolumeServerState + 2, // 3: volume_server_pb.SetStateResponse.state:type_name -> volume_server_pb.VolumeServerState + 46, // 4: volume_server_pb.ReceiveFileRequest.info:type_name -> volume_server_pb.ReceiveFileInfo + 80, // 5: volume_server_pb.VolumeEcShardsInfoResponse.ec_shard_infos:type_name -> volume_server_pb.EcShardInfo + 86, // 6: volume_server_pb.ReadVolumeFileStatusResponse.volume_info:type_name -> volume_server_pb.VolumeInfo + 85, // 7: volume_server_pb.VolumeInfo.files:type_name -> volume_server_pb.RemoteFile + 87, // 8: volume_server_pb.VolumeInfo.ec_shard_config:type_name -> volume_server_pb.EcShardConfig + 0, // 9: volume_server_pb.EcBitrotProtection.algorithm:type_name -> volume_server_pb.ChecksumAlgorithm + 87, // 10: volume_server_pb.EcBitrotProtection.ec_shard_config:type_name -> volume_server_pb.EcShardConfig + 89, // 11: volume_server_pb.EcBitrotProtection.shards:type_name -> volume_server_pb.EcShardChecksums + 85, // 12: volume_server_pb.OldVersionVolumeInfo.files:type_name -> volume_server_pb.RemoteFile + 83, // 13: volume_server_pb.VolumeServerStatusResponse.disk_statuses:type_name -> volume_server_pb.DiskStatus + 84, // 14: volume_server_pb.VolumeServerStatusResponse.memory_status:type_name -> volume_server_pb.MemStatus + 2, // 15: volume_server_pb.VolumeServerStatusResponse.state:type_name -> volume_server_pb.VolumeServerState + 111, // 16: volume_server_pb.FetchAndWriteNeedleRequest.replicas:type_name -> volume_server_pb.FetchAndWriteNeedleRequest.Replica + 120, // 17: volume_server_pb.FetchAndWriteNeedleRequest.remote_conf:type_name -> remote_pb.RemoteConf + 121, // 18: volume_server_pb.FetchAndWriteNeedleRequest.remote_location:type_name -> remote_pb.RemoteStorageLocation + 1, // 19: volume_server_pb.ScrubVolumeRequest.mode:type_name -> volume_server_pb.VolumeScrubMode + 1, // 20: volume_server_pb.ScrubEcVolumeRequest.mode:type_name -> volume_server_pb.VolumeScrubMode + 80, // 21: volume_server_pb.ScrubEcVolumeResponse.broken_shard_infos:type_name -> volume_server_pb.EcShardInfo + 112, // 22: volume_server_pb.QueryRequest.filter:type_name -> volume_server_pb.QueryRequest.Filter + 113, // 23: volume_server_pb.QueryRequest.input_serialization:type_name -> volume_server_pb.QueryRequest.InputSerialization + 114, // 24: volume_server_pb.QueryRequest.output_serialization:type_name -> volume_server_pb.QueryRequest.OutputSerialization + 115, // 25: volume_server_pb.QueryRequest.InputSerialization.csv_input:type_name -> volume_server_pb.QueryRequest.InputSerialization.CSVInput + 116, // 26: volume_server_pb.QueryRequest.InputSerialization.json_input:type_name -> volume_server_pb.QueryRequest.InputSerialization.JSONInput + 117, // 27: volume_server_pb.QueryRequest.InputSerialization.parquet_input:type_name -> volume_server_pb.QueryRequest.InputSerialization.ParquetInput + 118, // 28: volume_server_pb.QueryRequest.OutputSerialization.csv_output:type_name -> volume_server_pb.QueryRequest.OutputSerialization.CSVOutput + 119, // 29: volume_server_pb.QueryRequest.OutputSerialization.json_output:type_name -> volume_server_pb.QueryRequest.OutputSerialization.JSONOutput + 3, // 30: volume_server_pb.VolumeServer.BatchDelete:input_type -> volume_server_pb.BatchDeleteRequest + 7, // 31: volume_server_pb.VolumeServer.VacuumVolumeCheck:input_type -> volume_server_pb.VacuumVolumeCheckRequest + 9, // 32: volume_server_pb.VolumeServer.VacuumVolumeCompact:input_type -> volume_server_pb.VacuumVolumeCompactRequest + 11, // 33: volume_server_pb.VolumeServer.VacuumVolumeCommit:input_type -> volume_server_pb.VacuumVolumeCommitRequest + 13, // 34: volume_server_pb.VolumeServer.VacuumVolumeCleanup:input_type -> volume_server_pb.VacuumVolumeCleanupRequest + 15, // 35: volume_server_pb.VolumeServer.DeleteCollection:input_type -> volume_server_pb.DeleteCollectionRequest + 17, // 36: volume_server_pb.VolumeServer.AllocateVolume:input_type -> volume_server_pb.AllocateVolumeRequest + 19, // 37: volume_server_pb.VolumeServer.VolumeSyncStatus:input_type -> volume_server_pb.VolumeSyncStatusRequest + 21, // 38: volume_server_pb.VolumeServer.VolumeIncrementalCopy:input_type -> volume_server_pb.VolumeIncrementalCopyRequest + 23, // 39: volume_server_pb.VolumeServer.VolumeMount:input_type -> volume_server_pb.VolumeMountRequest + 25, // 40: volume_server_pb.VolumeServer.VolumeUnmount:input_type -> volume_server_pb.VolumeUnmountRequest + 27, // 41: volume_server_pb.VolumeServer.VolumeDelete:input_type -> volume_server_pb.VolumeDeleteRequest + 29, // 42: volume_server_pb.VolumeServer.VolumeMarkReadonly:input_type -> volume_server_pb.VolumeMarkReadonlyRequest + 31, // 43: volume_server_pb.VolumeServer.VolumeMarkWritable:input_type -> volume_server_pb.VolumeMarkWritableRequest + 33, // 44: volume_server_pb.VolumeServer.VolumeConfigure:input_type -> volume_server_pb.VolumeConfigureRequest + 35, // 45: volume_server_pb.VolumeServer.VolumeStatus:input_type -> volume_server_pb.VolumeStatusRequest + 37, // 46: volume_server_pb.VolumeServer.GetState:input_type -> volume_server_pb.GetStateRequest + 39, // 47: volume_server_pb.VolumeServer.SetState:input_type -> volume_server_pb.SetStateRequest + 41, // 48: volume_server_pb.VolumeServer.VolumeCopy:input_type -> volume_server_pb.VolumeCopyRequest + 81, // 49: volume_server_pb.VolumeServer.ReadVolumeFileStatus:input_type -> volume_server_pb.ReadVolumeFileStatusRequest + 43, // 50: volume_server_pb.VolumeServer.CopyFile:input_type -> volume_server_pb.CopyFileRequest + 45, // 51: volume_server_pb.VolumeServer.ReceiveFile:input_type -> volume_server_pb.ReceiveFileRequest + 48, // 52: volume_server_pb.VolumeServer.ReadNeedleBlob:input_type -> volume_server_pb.ReadNeedleBlobRequest + 50, // 53: volume_server_pb.VolumeServer.ReadNeedleMeta:input_type -> volume_server_pb.ReadNeedleMetaRequest + 52, // 54: volume_server_pb.VolumeServer.WriteNeedleBlob:input_type -> volume_server_pb.WriteNeedleBlobRequest + 54, // 55: volume_server_pb.VolumeServer.ReadAllNeedles:input_type -> volume_server_pb.ReadAllNeedlesRequest + 56, // 56: volume_server_pb.VolumeServer.VolumeTailSender:input_type -> volume_server_pb.VolumeTailSenderRequest + 58, // 57: volume_server_pb.VolumeServer.VolumeTailReceiver:input_type -> volume_server_pb.VolumeTailReceiverRequest + 60, // 58: volume_server_pb.VolumeServer.VolumeEcShardsGenerate:input_type -> volume_server_pb.VolumeEcShardsGenerateRequest + 62, // 59: volume_server_pb.VolumeServer.VolumeEcShardsRebuild:input_type -> volume_server_pb.VolumeEcShardsRebuildRequest + 64, // 60: volume_server_pb.VolumeServer.VolumeEcShardsCopy:input_type -> volume_server_pb.VolumeEcShardsCopyRequest + 66, // 61: volume_server_pb.VolumeServer.VolumeEcShardsDelete:input_type -> volume_server_pb.VolumeEcShardsDeleteRequest + 68, // 62: volume_server_pb.VolumeServer.VolumeEcShardsMount:input_type -> volume_server_pb.VolumeEcShardsMountRequest + 70, // 63: volume_server_pb.VolumeServer.VolumeEcShardsUnmount:input_type -> volume_server_pb.VolumeEcShardsUnmountRequest + 72, // 64: volume_server_pb.VolumeServer.VolumeEcShardRead:input_type -> volume_server_pb.VolumeEcShardReadRequest + 74, // 65: volume_server_pb.VolumeServer.VolumeEcBlobDelete:input_type -> volume_server_pb.VolumeEcBlobDeleteRequest + 76, // 66: volume_server_pb.VolumeServer.VolumeEcShardsToVolume:input_type -> volume_server_pb.VolumeEcShardsToVolumeRequest + 78, // 67: volume_server_pb.VolumeServer.VolumeEcShardsInfo:input_type -> volume_server_pb.VolumeEcShardsInfoRequest + 91, // 68: volume_server_pb.VolumeServer.VolumeTierMoveDatToRemote:input_type -> volume_server_pb.VolumeTierMoveDatToRemoteRequest + 93, // 69: volume_server_pb.VolumeServer.VolumeTierMoveDatFromRemote:input_type -> volume_server_pb.VolumeTierMoveDatFromRemoteRequest + 95, // 70: volume_server_pb.VolumeServer.VolumeServerStatus:input_type -> volume_server_pb.VolumeServerStatusRequest + 97, // 71: volume_server_pb.VolumeServer.VolumeServerLeave:input_type -> volume_server_pb.VolumeServerLeaveRequest + 99, // 72: volume_server_pb.VolumeServer.FetchAndWriteNeedle:input_type -> volume_server_pb.FetchAndWriteNeedleRequest + 101, // 73: volume_server_pb.VolumeServer.ScrubVolume:input_type -> volume_server_pb.ScrubVolumeRequest + 103, // 74: volume_server_pb.VolumeServer.ScrubEcVolume:input_type -> volume_server_pb.ScrubEcVolumeRequest + 105, // 75: volume_server_pb.VolumeServer.Query:input_type -> volume_server_pb.QueryRequest + 107, // 76: volume_server_pb.VolumeServer.VolumeNeedleStatus:input_type -> volume_server_pb.VolumeNeedleStatusRequest + 109, // 77: volume_server_pb.VolumeServer.Ping:input_type -> volume_server_pb.PingRequest + 4, // 78: volume_server_pb.VolumeServer.BatchDelete:output_type -> volume_server_pb.BatchDeleteResponse + 8, // 79: volume_server_pb.VolumeServer.VacuumVolumeCheck:output_type -> volume_server_pb.VacuumVolumeCheckResponse + 10, // 80: volume_server_pb.VolumeServer.VacuumVolumeCompact:output_type -> volume_server_pb.VacuumVolumeCompactResponse + 12, // 81: volume_server_pb.VolumeServer.VacuumVolumeCommit:output_type -> volume_server_pb.VacuumVolumeCommitResponse + 14, // 82: volume_server_pb.VolumeServer.VacuumVolumeCleanup:output_type -> volume_server_pb.VacuumVolumeCleanupResponse + 16, // 83: volume_server_pb.VolumeServer.DeleteCollection:output_type -> volume_server_pb.DeleteCollectionResponse + 18, // 84: volume_server_pb.VolumeServer.AllocateVolume:output_type -> volume_server_pb.AllocateVolumeResponse + 20, // 85: volume_server_pb.VolumeServer.VolumeSyncStatus:output_type -> volume_server_pb.VolumeSyncStatusResponse + 22, // 86: volume_server_pb.VolumeServer.VolumeIncrementalCopy:output_type -> volume_server_pb.VolumeIncrementalCopyResponse + 24, // 87: volume_server_pb.VolumeServer.VolumeMount:output_type -> volume_server_pb.VolumeMountResponse + 26, // 88: volume_server_pb.VolumeServer.VolumeUnmount:output_type -> volume_server_pb.VolumeUnmountResponse + 28, // 89: volume_server_pb.VolumeServer.VolumeDelete:output_type -> volume_server_pb.VolumeDeleteResponse + 30, // 90: volume_server_pb.VolumeServer.VolumeMarkReadonly:output_type -> volume_server_pb.VolumeMarkReadonlyResponse + 32, // 91: volume_server_pb.VolumeServer.VolumeMarkWritable:output_type -> volume_server_pb.VolumeMarkWritableResponse + 34, // 92: volume_server_pb.VolumeServer.VolumeConfigure:output_type -> volume_server_pb.VolumeConfigureResponse + 36, // 93: volume_server_pb.VolumeServer.VolumeStatus:output_type -> volume_server_pb.VolumeStatusResponse + 38, // 94: volume_server_pb.VolumeServer.GetState:output_type -> volume_server_pb.GetStateResponse + 40, // 95: volume_server_pb.VolumeServer.SetState:output_type -> volume_server_pb.SetStateResponse + 42, // 96: volume_server_pb.VolumeServer.VolumeCopy:output_type -> volume_server_pb.VolumeCopyResponse + 82, // 97: volume_server_pb.VolumeServer.ReadVolumeFileStatus:output_type -> volume_server_pb.ReadVolumeFileStatusResponse + 44, // 98: volume_server_pb.VolumeServer.CopyFile:output_type -> volume_server_pb.CopyFileResponse + 47, // 99: volume_server_pb.VolumeServer.ReceiveFile:output_type -> volume_server_pb.ReceiveFileResponse + 49, // 100: volume_server_pb.VolumeServer.ReadNeedleBlob:output_type -> volume_server_pb.ReadNeedleBlobResponse + 51, // 101: volume_server_pb.VolumeServer.ReadNeedleMeta:output_type -> volume_server_pb.ReadNeedleMetaResponse + 53, // 102: volume_server_pb.VolumeServer.WriteNeedleBlob:output_type -> volume_server_pb.WriteNeedleBlobResponse + 55, // 103: volume_server_pb.VolumeServer.ReadAllNeedles:output_type -> volume_server_pb.ReadAllNeedlesResponse + 57, // 104: volume_server_pb.VolumeServer.VolumeTailSender:output_type -> volume_server_pb.VolumeTailSenderResponse + 59, // 105: volume_server_pb.VolumeServer.VolumeTailReceiver:output_type -> volume_server_pb.VolumeTailReceiverResponse + 61, // 106: volume_server_pb.VolumeServer.VolumeEcShardsGenerate:output_type -> volume_server_pb.VolumeEcShardsGenerateResponse + 63, // 107: volume_server_pb.VolumeServer.VolumeEcShardsRebuild:output_type -> volume_server_pb.VolumeEcShardsRebuildResponse + 65, // 108: volume_server_pb.VolumeServer.VolumeEcShardsCopy:output_type -> volume_server_pb.VolumeEcShardsCopyResponse + 67, // 109: volume_server_pb.VolumeServer.VolumeEcShardsDelete:output_type -> volume_server_pb.VolumeEcShardsDeleteResponse + 69, // 110: volume_server_pb.VolumeServer.VolumeEcShardsMount:output_type -> volume_server_pb.VolumeEcShardsMountResponse + 71, // 111: volume_server_pb.VolumeServer.VolumeEcShardsUnmount:output_type -> volume_server_pb.VolumeEcShardsUnmountResponse + 73, // 112: volume_server_pb.VolumeServer.VolumeEcShardRead:output_type -> volume_server_pb.VolumeEcShardReadResponse + 75, // 113: volume_server_pb.VolumeServer.VolumeEcBlobDelete:output_type -> volume_server_pb.VolumeEcBlobDeleteResponse + 77, // 114: volume_server_pb.VolumeServer.VolumeEcShardsToVolume:output_type -> volume_server_pb.VolumeEcShardsToVolumeResponse + 79, // 115: volume_server_pb.VolumeServer.VolumeEcShardsInfo:output_type -> volume_server_pb.VolumeEcShardsInfoResponse + 92, // 116: volume_server_pb.VolumeServer.VolumeTierMoveDatToRemote:output_type -> volume_server_pb.VolumeTierMoveDatToRemoteResponse + 94, // 117: volume_server_pb.VolumeServer.VolumeTierMoveDatFromRemote:output_type -> volume_server_pb.VolumeTierMoveDatFromRemoteResponse + 96, // 118: volume_server_pb.VolumeServer.VolumeServerStatus:output_type -> volume_server_pb.VolumeServerStatusResponse + 98, // 119: volume_server_pb.VolumeServer.VolumeServerLeave:output_type -> volume_server_pb.VolumeServerLeaveResponse + 100, // 120: volume_server_pb.VolumeServer.FetchAndWriteNeedle:output_type -> volume_server_pb.FetchAndWriteNeedleResponse + 102, // 121: volume_server_pb.VolumeServer.ScrubVolume:output_type -> volume_server_pb.ScrubVolumeResponse + 104, // 122: volume_server_pb.VolumeServer.ScrubEcVolume:output_type -> volume_server_pb.ScrubEcVolumeResponse + 106, // 123: volume_server_pb.VolumeServer.Query:output_type -> volume_server_pb.QueriedStripe + 108, // 124: volume_server_pb.VolumeServer.VolumeNeedleStatus:output_type -> volume_server_pb.VolumeNeedleStatusResponse + 110, // 125: volume_server_pb.VolumeServer.Ping:output_type -> volume_server_pb.PingResponse + 78, // [78:126] is the sub-list for method output_type + 30, // [30:78] is the sub-list for method input_type + 30, // [30:30] is the sub-list for extension type_name + 30, // [30:30] is the sub-list for extension extendee + 0, // [0:30] is the sub-list for field type_name } func init() { file_volume_server_proto_init() } @@ -7606,8 +7846,8 @@ func file_volume_server_proto_init() { File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_volume_server_proto_rawDesc), len(file_volume_server_proto_rawDesc)), - NumEnums: 1, - NumMessages: 116, + NumEnums: 2, + NumMessages: 118, NumExtensions: 0, NumServices: 1, }, diff --git a/weed/server/volume_grpc_erasure_coding.go b/weed/server/volume_grpc_erasure_coding.go index 4882101db..b0b1ebc05 100644 --- a/weed/server/volume_grpc_erasure_coding.go +++ b/weed/server/volume_grpc_erasure_coding.go @@ -7,6 +7,7 @@ import ( "math" "os" "path" + "path/filepath" "strconv" "strings" "time" @@ -84,6 +85,7 @@ func (vs *VolumeServer) VolumeEcShardsGenerate(ctx context.Context, req *volume_ os.Remove(baseFileName + ecCtx.ToExt(i)) } os.Remove(v.IndexFileName() + ".ecx") + os.Remove(erasure_coding.BitrotSidecarPath(baseFileName, 0)) }() // IMPORTANT: Generate .ecx BEFORE EC shards to prevent a race condition. @@ -106,8 +108,19 @@ func (vs *VolumeServer) VolumeEcShardsGenerate(ctx context.Context, req *volume_ datSize, _, _ := v.FileStat() // write .ec00 ~ .ec[TotalShards-1] files using context - if err := erasure_coding.WriteEcFilesWithContext(baseFileName, ecCtx); err != nil { - return nil, fmt.Errorf("WriteEcFilesWithContext %s: %v", baseFileName, err) + ecBitrot, err := erasure_coding.WriteEcFiles(baseFileName, ecCtx) + if err != nil { + return nil, fmt.Errorf("WriteEcFiles %s: %v", baseFileName, err) + } + // Persist the generation-0 bitrot checksum sidecar (.ecsum) alongside + // the shards so it travels with them during distribution (copy_ecsum_file). + // The source loading its own canonical sidecar is correct — it holds all + // shards and a complete manifest. Best-effort: a failed sidecar write leaves + // the generation unprotected rather than failing the encode. + if erasure_coding.BitrotProtectionEnabled && ecBitrot != nil { + if serr := erasure_coding.SaveBitrotSidecar(erasure_coding.BitrotSidecarPath(baseFileName, 0), ecBitrot); serr != nil { + glog.Warningf("failed to write EC bitrot sidecar for volume %d: %v", req.VolumeId, serr) + } } // write .vif files @@ -203,9 +216,11 @@ func (vs *VolumeServer) VolumeEcShardsRebuild(ctx context.Context, req *volume_s additionalDirs = append(additionalDirs, otherLocation.Directory) } - // Rebuild missing EC files, searching all disk locations for input shards + // Rebuild missing EC files, searching all disk locations for input shards. + // Present input shards are verified against the bitrot sidecar (when present) + // and corrupt ones are regenerated; unsafe_ignore_sidecar bypasses the guard. dataBaseFileName := path.Join(rebuildDataDir, baseFileName) - if generatedShardIds, err := erasure_coding.RebuildEcFiles(dataBaseFileName, additionalDirs...); err != nil { + if generatedShardIds, err := erasure_coding.RebuildEcFiles(dataBaseFileName, erasure_coding.BackgroundECContext(), req.UnsafeIgnoreSidecar, additionalDirs...); err != nil { return nil, fmt.Errorf("RebuildEcFiles %s: %v", dataBaseFileName, err) } else { rebuiltShardIds = generatedShardIds @@ -219,6 +234,30 @@ func (vs *VolumeServer) VolumeEcShardsRebuild(ctx context.Context, req *volume_s return nil, fmt.Errorf("RebuildEcxFile %s: %v", indexBaseFileName, err) } + // Opportunistic bitrot backfill: if protection is enabled, no sidecar exists + // yet (a volume encoded before this feature), and this rebuilder can reach + // every shard, compute and write a generation-0 sidecar. The TOFU baseline + // blesses current bytes; ComputeProtectionFromShards refuses a partial + // manifest, so a multi-server rebuild that cannot reach all shards just skips. + if erasure_coding.BitrotProtectionEnabled { + sidecarPath := erasure_coding.BitrotSidecarPath(dataBaseFileName, 0) + if _, statErr := os.Stat(sidecarPath); os.IsNotExist(statErr) { + ctx := erasure_coding.NewDefaultECContext("", 0) + if vi, _, found, _ := volume_info.MaybeLoadVolumeInfo(dataBaseFileName + ".vif"); found && vi.EcShardConfig != nil { + if ds, ps := int(vi.EcShardConfig.DataShards), int(vi.EcShardConfig.ParityShards); ds > 0 && ps > 0 && ds+ps <= erasure_coding.MaxShardCount { + ctx = &erasure_coding.ECContext{DataShards: ds, ParityShards: ps} + } + } + if prot, berr := erasure_coding.ComputeProtectionFromShards(dataBaseFileName, ctx, 0, additionalDirs); berr != nil { + glog.V(2).Infof("bitrot backfill skipped for %s: %v", dataBaseFileName, berr) + } else if werr := erasure_coding.SaveBitrotSidecar(sidecarPath, prot); werr != nil { + glog.Warningf("bitrot backfill: write sidecar for %s: %v", dataBaseFileName, werr) + } else { + glog.V(0).Infof("bitrot backfill: wrote sidecar for %s after rebuild", dataBaseFileName) + } + } + } + return &volume_server_pb.VolumeEcShardsRebuildResponse{ RebuiltShardIds: rebuiltShardIds, }, nil @@ -330,6 +369,17 @@ func (vs *VolumeServer) VolumeEcShardsCopy(ctx context.Context, req *volume_serv return err } } + + if req.CopyEcsumFile { + // Propagate the generation-0 bitrot checksum sidecar when the source + // has one. This non-2PC copy path (balance / fresh-encode / rebuild + // distribution) has no Prepare backstop, and fresh-encode sidecar + // writes are best-effort, so a missing source sidecar is a no-op + // (ignore-not-found): the holder is simply unprotected. + if _, err := vs.doCopyFile(client, true, req.Collection, req.VolumeId, math.MaxUint32, math.MaxInt64, dataBaseFileName, erasure_coding.BitrotSidecarExt, false, true, nil); err != nil { + return fmt.Errorf("VolumeEcShardsCopy volume %d: copy %s sidecar: %w", req.VolumeId, erasure_coding.BitrotSidecarExt, err) + } + } return nil }) if err != nil { @@ -393,27 +443,54 @@ func deleteEcShardIdsForEachLocation(bName string, location *storage.DiskLocatio return err } - if hasEcxFile && existingShardCount == 0 { - // Remove .ecx/.ecj from both idx and data directories - // since they may be in either location depending on when -dir.idx was configured - if err := os.Remove(indexBaseFilename + ".ecx"); err != nil && !os.IsNotExist(err) { - return err - } - os.Remove(indexBaseFilename + ".ecj") + if existingShardCount == 0 { + // The whole EC generation is gone on this disk. + + // Remove the bitrot checksum sidecar(s) (.ecsum and any .ecsum.v) + // from both dirs. This is gated on the shards being gone, NOT on a + // stray .ecx still being present: a sidecar whose shards have all been + // deleted is orphaned and must go even when no .ecx remains, or it + // leaks. The per-shard-id delete that ec.rebuild uses for + // copied-survivor cleanup leaves shards behind, so this guard does not + // fire there. + removeBitrotSidecars(dataBaseFilename) if location.IdxDirectory != location.Directory { - os.Remove(dataBaseFilename + ".ecx") - os.Remove(dataBaseFilename + ".ecj") + removeBitrotSidecars(indexBaseFilename) } - if !hasIdxFile { - // .vif is used for ec volumes and normal volumes - os.Remove(dataBaseFilename + ".vif") + if hasEcxFile { + // Remove .ecx/.ecj from both idx and data directories + // since they may be in either location depending on when -dir.idx was configured + if err := os.Remove(indexBaseFilename + ".ecx"); err != nil && !os.IsNotExist(err) { + return err + } + os.Remove(indexBaseFilename + ".ecj") + if location.IdxDirectory != location.Directory { + os.Remove(dataBaseFilename + ".ecx") + os.Remove(dataBaseFilename + ".ecj") + } + + if !hasIdxFile { + // .vif is used for ec volumes and normal volumes + os.Remove(dataBaseFilename + ".vif") + } } } return nil } +// removeBitrotSidecars removes the legacy .ecsum and any versioned +// .ecsum.v sidecars. Best-effort; logs nothing on absence. +func removeBitrotSidecars(baseFilename string) { + os.Remove(baseFilename + erasure_coding.BitrotSidecarExt) + if matches, _ := filepath.Glob(baseFilename + erasure_coding.BitrotSidecarExt + ".v*"); matches != nil { + for _, m := range matches { + os.Remove(m) + } + } +} + func checkEcVolumeStatus(bName string, location *storage.DiskLocation) (hasEcxFile bool, hasIdxFile bool, existingShardCount int, err error) { // check whether to delete the .ecx and .ecj file also fileInfos, err := os.ReadDir(location.Directory) @@ -681,6 +758,13 @@ func (vs *VolumeServer) VolumeEcShardsToVolume(ctx context.Context, req *volume_ return nil, fmt.Errorf("WriteIdxFileFromEcIndex %s: %v", v.IndexBaseFileName(), err) } + // The EC generation is gone; drop its bitrot sidecar(s) so a later EC + // re-encode cannot mistake a stale .ecsum for protection. + removeBitrotSidecars(dataBaseFileName) + if indexBaseFileName != dataBaseFileName { + removeBitrotSidecars(indexBaseFileName) + } + var volumeLocation *storage.DiskLocation for _, location := range vs.store.Locations { if candidate, found := location.FindEcVolume(needle.VolumeId(req.VolumeId)); found && candidate == v { diff --git a/weed/server/volume_grpc_scrub.go b/weed/server/volume_grpc_scrub.go index 27afe2dac..c306944ae 100644 --- a/weed/server/volume_grpc_scrub.go +++ b/weed/server/volume_grpc_scrub.go @@ -125,6 +125,12 @@ func (vs *VolumeServer) ScrubEcVolume(ctx context.Context, req *volume_server_pb files, shardInfos, serrs = v.ScrubLocal() case volume_server_pb.VolumeScrubMode_FULL: files, shardInfos, serrs = vs.store.ScrubEcVolume(v.VolumeId) + case volume_server_pb.VolumeScrubMode_CHECKSUM: + // Verify each local shard's raw bytes against the bitrot sidecar, + // exercising cold parity shards. Read-only. ChecksumScrub's first + // return is blocks scanned, not files — discard it so TotalFiles + // (a needle/file count) isn't inflated by the block count. + _, shardInfos, serrs = v.ChecksumScrub() default: return nil, fmt.Errorf("unsupported EC volume scrub mode %d", m) } diff --git a/weed/shell/command_ec_common.go b/weed/shell/command_ec_common.go index 1690e9801..199641442 100644 --- a/weed/shell/command_ec_common.go +++ b/weed/shell/command_ec_common.go @@ -350,6 +350,7 @@ func oneServerCopyAndMountEcShardsFromSource(grpcDialOption grpc.DialOption, CopyEcxFile: true, CopyEcjFile: true, CopyVifFile: true, + CopyEcsumFile: true, // propagate the bitrot sidecar with the shards (no-op if the source has none) SourceDataNode: string(existingLocation), DiskId: destDiskId, }) diff --git a/weed/shell/command_ec_scrub.go b/weed/shell/command_ec_scrub.go index 24117464b..ee3235972 100644 --- a/weed/shell/command_ec_scrub.go +++ b/weed/shell/command_ec_scrub.go @@ -49,7 +49,7 @@ func (c *commandEcVolumeScrub) Do(args []string, commandEnv *CommandEnv, writer volScrubCommand := flag.NewFlagSet(c.Name(), flag.ContinueOnError) nodesStr := volScrubCommand.String("node", "", "comma-separated list of volume server : (optional)") volumeIDsStr := volScrubCommand.String("volumeId", "", "comma-separated EC volume IDs to process (optional)") - mode := volScrubCommand.String("mode", "local", "scrubbing mode (index/local/full)") + mode := volScrubCommand.String("mode", "local", "scrubbing mode (index/local/full/checksum)") maxParallelization := volScrubCommand.Int("maxParallelization", DefaultMaxParallelization, "run up to X tasks in parallel, whenever possible") if err = volScrubCommand.Parse(args); err != nil { @@ -96,6 +96,8 @@ func (c *commandEcVolumeScrub) Do(args []string, commandEnv *CommandEnv, writer c.mode = volume_server_pb.VolumeScrubMode_LOCAL case "FULL": c.mode = volume_server_pb.VolumeScrubMode_FULL + case "CHECKSUM": + c.mode = volume_server_pb.VolumeScrubMode_CHECKSUM default: return fmt.Errorf("unsupported scrubbing mode %q", *mode) } diff --git a/weed/storage/disk_location_ec_realworld_test.go b/weed/storage/disk_location_ec_realworld_test.go index ea92d3f00..42ce29bd7 100644 --- a/weed/storage/disk_location_ec_realworld_test.go +++ b/weed/storage/disk_location_ec_realworld_test.go @@ -86,7 +86,7 @@ func TestCalculateExpectedShardSizeWithRealEncoding(t *testing.T) { expectedShardSize := calculateExpectedShardSize(tt.datFileSize, erasure_coding.DataShardsCount) // Run actual EC encoding - err = erasure_coding.WriteEcFiles(baseFileName) + _, err = erasure_coding.WriteEcFiles(baseFileName, erasure_coding.BackgroundECContext()) if err != nil { t.Fatalf("Failed to encode EC files: %v", err) } @@ -167,7 +167,7 @@ func TestCalculateExpectedShardSizeEdgeCases(t *testing.T) { expectedShardSize := calculateExpectedShardSize(tt.datFileSize, erasure_coding.DataShardsCount) // Run actual EC encoding - err = erasure_coding.WriteEcFiles(baseFileName) + _, err = erasure_coding.WriteEcFiles(baseFileName, erasure_coding.BackgroundECContext()) if err != nil { t.Fatalf("Failed to encode EC files: %v", err) } diff --git a/weed/storage/erasure_coding/ec_bitrot.go b/weed/storage/erasure_coding/ec_bitrot.go new file mode 100644 index 000000000..e4f820fe4 --- /dev/null +++ b/weed/storage/erasure_coding/ec_bitrot.go @@ -0,0 +1,554 @@ +package erasure_coding + +// EC bitrot detection — checksum sidecar. +// +// A per-volume sidecar file stores a CRC32C (Castagnoli) checksum for every +// fixed-size block of every EC shard, so a scrub (and the reconstruction path) +// can detect silent disk corruption in any shard — including cold parity shards +// that are never read during normal serving. +// +// The sidecar is OPTIONAL: an absent or generation-mismatched sidecar simply +// means "feature off" for that generation, so old binaries, JSON-only Rust +// nodes, and rollback deployments ignore it and degrade gracefully. See +// EC_BITROT_DETECTION_DESIGN.md. +// +// On-disk layout of .ecsum (legacy/generation 0) and .ecsum.v: +// +// [ magic(4) | format_version(2) | payload_len(4) | payload_crc32c(4) ] [ proto payload ] +// +// The header's payload_crc32c lets a loader detect corruption of the sidecar +// itself BEFORE trusting any contents, so a rotted sidecar can never be +// mistaken for shard corruption. + +import ( + "crypto/rand" + "encoding/binary" + "fmt" + "io" + "math/bits" + "os" + "path/filepath" + + "github.com/seaweedfs/seaweedfs/weed/glog" + "github.com/seaweedfs/seaweedfs/weed/pb/volume_server_pb" + "github.com/seaweedfs/seaweedfs/weed/storage/needle" + "google.golang.org/protobuf/proto" +) + +const ( + // BitrotSidecarExt is the canonical extension for the checksum sidecar. + // Generation 0 (legacy/fresh encode) uses ".ecsum"; vacuum generation + // N uses ".ecsum.v", mirroring the .vif/.ecx versioned convention. + BitrotSidecarExt = ".ecsum" + + // DefaultBitrotBlockSize is the default checksum granularity (16 MiB). It is + // a power-of-two multiple of ErasureCodingSmallBlockSize (1 MiB) and keeps + // the sidecar tiny (~11 KB for a 30 GB volume) while localizing corruption + // to a 16 MiB region. + DefaultBitrotBlockSize = 16 * 1024 * 1024 + + bitrotMagic uint32 = 0x45435355 // "ECSU" + bitrotFormatVersion uint16 = 1 + bitrotHeaderSize = 14 // magic(4)+version(2)+payload_len(4)+payload_crc32c(4) +) + +// Config knobs (wired to volume-server flags). Defaults: protection on for new +// encodes, 16 MiB block granularity. +var ( + // BitrotProtectionEnabled controls whether encode/vacuum write a sidecar. + // When false, generations are produced unprotected (no sidecar), which is a + // legitimate "off" state. Detection on already-protected generations is + // unaffected. + BitrotProtectionEnabled = true + // BitrotBlockSize is the checksum block granularity used for new sidecars. + // Must remain a power-of-two multiple of 1 MiB. + BitrotBlockSize int64 = DefaultBitrotBlockSize +) + +// BitrotStatus is the resolved protection state of an EC volume's active +// generation after loading and validating its sidecar. +type BitrotStatus int + +const ( + // BitrotOff: no sidecar, or a sidecar that does not describe the active + // generation. The generation is unprotected; this is NOT corruption. + BitrotOff BitrotStatus = iota + // BitrotOn: a complete, well-formed, generation-matching sidecar is loaded. + BitrotOn + // BitrotInvalid: a generation-matching sidecar that is malformed, incomplete, + // or self-integrity-failed. The generation is unprotected pending repair of + // the sidecar, and an integrity alarm should fire. The rebuild path treats + // this as fail-closed (see ec_encoder rebuild verify). + BitrotInvalid +) + +func (s BitrotStatus) String() string { + switch s { + case BitrotOn: + return "on" + case BitrotInvalid: + return "invalid" + default: + return "off" + } +} + +// BitrotSidecarPath returns the sidecar path for a base file name and EC +// generation. Generation 0 is the un-suffixed legacy path; generation N>0 is +// the versioned path, consistent with how .vif/.ecx are versioned by the 2PC +// switch. +func BitrotSidecarPath(baseFileName string, generation uint32) string { + if generation == 0 { + return baseFileName + BitrotSidecarExt + } + return fmt.Sprintf("%s%s.v%d", baseFileName, BitrotSidecarExt, generation) +} + +// NewEncodeUUID returns a fresh random per-encode identity used to detect a +// stale sidecar left behind by an in-place re-encode. +func NewEncodeUUID() []byte { + b := make([]byte, 16) + if _, err := rand.Read(b); err != nil { + // rand.Read essentially never fails; fall back to a zero UUID rather + // than aborting an encode over entropy. Stale detection still has the + // other defenses (wholesale-mismatch cap + RS arbiter). + glog.Warningf("ec bitrot: failed to read encode uuid entropy: %v", err) + } + return b +} + +// isPow2MultipleOf1MiB reports whether block_size is a power-of-two multiple of +// 1 MiB (i.e. a power of two that is at least 1 MiB). +func isPow2MultipleOf1MiB(blockSize uint32) bool { + return blockSize >= (1<<20) && bits.OnesCount32(blockSize) == 1 +} + +// shardChecksumBuilder accumulates the per-block CRC32C of a single shard's byte +// stream as it is written. It tolerates arbitrary chunk sizes that cross block +// boundaries, so it works for both the 256 KiB encode buffers and the 1 MiB +// rebuild buffers. +type shardChecksumBuilder struct { + blockSize int64 + cur needle.CRC + curLen int64 + total int64 + blocks []uint32 +} + +func newShardChecksumBuilder(blockSize int64) *shardChecksumBuilder { + return &shardChecksumBuilder{blockSize: blockSize} +} + +func (b *shardChecksumBuilder) write(p []byte) { + for len(p) > 0 { + room := b.blockSize - b.curLen + n := int64(len(p)) + if n > room { + n = room + } + b.cur = b.cur.Update(p[:n]) + b.curLen += n + b.total += n + p = p[n:] + if b.curLen == b.blockSize { + b.blocks = append(b.blocks, uint32(b.cur)) + b.cur = 0 + b.curLen = 0 + } + } +} + +// finalize flushes any partial last block and returns the covered size and the +// packed little-endian uint32 CRC array. +func (b *shardChecksumBuilder) finalize() (coveredSize int64, packed []byte) { + if b.curLen > 0 { + b.blocks = append(b.blocks, uint32(b.cur)) + b.cur = 0 + b.curLen = 0 + } + return b.total, packUint32LE(b.blocks) +} + +// buildProtectionFromBuilders assembles a generation-0 EcBitrotProtection from +// the per-shard checksum builders produced during an encode pass. Callers that +// produce a versioned generation (vacuum) set prot.Generation afterwards. A +// fresh encode_uuid is minted on every call so an in-place re-encode is +// distinguishable from a stale sidecar. +func buildProtectionFromBuilders(ctx *ECContext, builders []*shardChecksumBuilder, blockSize int64) *volume_server_pb.EcBitrotProtection { + shards := make([]*volume_server_pb.EcShardChecksums, 0, len(builders)) + for id, b := range builders { + covered, packed := b.finalize() + shards = append(shards, &volume_server_pb.EcShardChecksums{ + ShardId: uint32(id), + CoveredSize: covered, + BlockCrc32C: packed, + }) + } + return &volume_server_pb.EcBitrotProtection{ + Algorithm: volume_server_pb.ChecksumAlgorithm_CHECKSUM_CRC32C, + BlockSize: uint32(blockSize), + Generation: 0, + EcShardConfig: &volume_server_pb.EcShardConfig{ + DataShards: uint32(ctx.DataShards), + ParityShards: uint32(ctx.ParityShards), + }, + Shards: shards, + EncodeUuid: NewEncodeUUID(), + } +} + +func packUint32LE(vals []uint32) []byte { + out := make([]byte, len(vals)*4) + for i, v := range vals { + binary.LittleEndian.PutUint32(out[i*4:], v) + } + return out +} + +func unpackUint32LE(b []byte) []uint32 { + out := make([]uint32, len(b)/4) + for i := range out { + out[i] = binary.LittleEndian.Uint32(b[i*4:]) + } + return out +} + +// expectedBlockCount returns ceil(coveredSize/blockSize). +func expectedBlockCount(coveredSize, blockSize int64) int { + if blockSize <= 0 { + return 0 + } + return int((coveredSize + blockSize - 1) / blockSize) +} + +// SaveBitrotSidecar atomically writes prot to path, wrapped in the on-disk +// header with a CRC32C over the serialized payload. +func SaveBitrotSidecar(path string, prot *volume_server_pb.EcBitrotProtection) error { + payload, err := proto.Marshal(prot) + if err != nil { + return fmt.Errorf("marshal bitrot sidecar: %w", err) + } + // The header records payload_len as a uint32 and the allocation below adds + // it to a constant. Bound the payload well under any overflow (a real + // manifest is a few KB) so neither the length field nor make() can wrap. + const maxBitrotPayloadSize = 1 << 30 // 1 GiB, vastly above any real sidecar + if len(payload) > maxBitrotPayloadSize { + return fmt.Errorf("bitrot sidecar payload too large: %d bytes", len(payload)) + } + buf := make([]byte, bitrotHeaderSize+len(payload)) + binary.BigEndian.PutUint32(buf[0:4], bitrotMagic) + binary.BigEndian.PutUint16(buf[4:6], bitrotFormatVersion) + binary.BigEndian.PutUint32(buf[6:10], uint32(len(payload))) + binary.BigEndian.PutUint32(buf[10:14], uint32(needle.NewCRC(payload))) + copy(buf[bitrotHeaderSize:], payload) + + tmp := path + ".tmp" + if err := os.WriteFile(tmp, buf, 0644); err != nil { + return fmt.Errorf("write bitrot sidecar tmp %s: %w", tmp, err) + } + if err := os.Rename(tmp, path); err != nil { + os.Remove(tmp) + return fmt.Errorf("rename bitrot sidecar %s: %w", path, err) + } + return nil +} + +// LoadBitrotSidecar reads and self-integrity-checks a sidecar file. It returns +// the parsed message, or an error if the file is missing, truncated, has a bad +// magic/version, or fails the payload CRC. A self-integrity failure is a +// sidecar-integrity problem (caller maps it to BitrotInvalid), never a shard +// corruption signal. +func LoadBitrotSidecar(path string) (*volume_server_pb.EcBitrotProtection, error) { + data, err := os.ReadFile(path) + if err != nil { + return nil, err + } + if len(data) < bitrotHeaderSize { + return nil, fmt.Errorf("bitrot sidecar %s too short (%d bytes)", path, len(data)) + } + if magic := binary.BigEndian.Uint32(data[0:4]); magic != bitrotMagic { + return nil, fmt.Errorf("bitrot sidecar %s bad magic %#x", path, magic) + } + if ver := binary.BigEndian.Uint16(data[4:6]); ver != bitrotFormatVersion { + return nil, fmt.Errorf("bitrot sidecar %s unsupported format version %d", path, ver) + } + payloadLen := binary.BigEndian.Uint32(data[6:10]) + wantCRC := binary.BigEndian.Uint32(data[10:14]) + payload := data[bitrotHeaderSize:] + if int(payloadLen) != len(payload) { + return nil, fmt.Errorf("bitrot sidecar %s length mismatch: header %d, actual %d", path, payloadLen, len(payload)) + } + if got := uint32(needle.NewCRC(payload)); got != wantCRC { + return nil, fmt.Errorf("bitrot sidecar %s self-integrity CRC mismatch: header %#x, computed %#x", path, wantCRC, got) + } + prot := &volume_server_pb.EcBitrotProtection{} + if err := proto.Unmarshal(payload, prot); err != nil { + return nil, fmt.Errorf("unmarshal bitrot sidecar %s: %w", path, err) + } + return prot, nil +} + +// ValidateBitrotManifest performs the disk-free manifest/syntax checks that +// every loader runs: supported algorithm, valid block size, exactly one entry +// per shard id in the active layout (no duplicates, no out-of-range ids), +// positive covered_size, and a packed-CRC count consistent with covered_size. +// It does NOT compare covered_size against on-disk shard lengths — that is a +// per-node physical check done only for locally-held shards. +func ValidateBitrotManifest(prot *volume_server_pb.EcBitrotProtection, dataShards, parityShards int) error { + if prot.Algorithm != volume_server_pb.ChecksumAlgorithm_CHECKSUM_CRC32C { + return fmt.Errorf("unsupported checksum algorithm %v", prot.Algorithm) + } + bs := int64(prot.BlockSize) + if !isPow2MultipleOf1MiB(prot.BlockSize) { + return fmt.Errorf("invalid block_size %d (must be a power-of-two multiple of 1 MiB)", prot.BlockSize) + } + total := dataShards + parityShards + if total <= 0 || total > MaxShardCount { + return fmt.Errorf("invalid active layout: data=%d parity=%d", dataShards, parityShards) + } + if len(prot.Shards) != total { + return fmt.Errorf("incomplete manifest: %d shard entries, expected %d", len(prot.Shards), total) + } + seen := make([]bool, MaxShardCount) + for _, s := range prot.Shards { + if s.ShardId >= uint32(total) { + return fmt.Errorf("shard id %d out of range [0,%d)", s.ShardId, total) + } + if seen[s.ShardId] { + return fmt.Errorf("duplicate shard id %d", s.ShardId) + } + seen[s.ShardId] = true + if s.CoveredSize <= 0 { + return fmt.Errorf("shard %d has non-positive covered_size %d", s.ShardId, s.CoveredSize) + } + wantCount := expectedBlockCount(s.CoveredSize, bs) + if len(s.BlockCrc32C) != wantCount*4 { + return fmt.Errorf("shard %d crc count mismatch: %d bytes, expected %d (covered_size=%d block_size=%d)", + s.ShardId, len(s.BlockCrc32C), wantCount*4, s.CoveredSize, prot.BlockSize) + } + } + return nil +} + +// shardChecksums returns the EcShardChecksums entry for a shard id, or nil. +func shardChecksums(prot *volume_server_pb.EcBitrotProtection, shardId uint32) *volume_server_pb.EcShardChecksums { + for _, s := range prot.Shards { + if s.ShardId == shardId { + return s + } + } + return nil +} + +// verifyShardFileBlocks reads a shard file at path in block_size chunks and +// compares each block's CRC32C against the manifest entry. It returns the list +// of mismatching block indices (empty == clean) and a fatal error only for I/O +// problems or a covered_size/length mismatch (which is itself corruption of the +// shard). It does not interpret the result — the caller (scrub / rebuild) +// arbitrates shard-vs-sidecar via Reed-Solomon before acting. +func verifyShardFileBlocks(path string, entry *volume_server_pb.EcShardChecksums, blockSize int64) (mismatched []int, err error) { + f, err := os.Open(path) + if err != nil { + return nil, err + } + defer f.Close() + fi, err := f.Stat() + if err != nil { + return nil, err + } + if fi.Size() != entry.CoveredSize { + // Length drift (truncation or unexpected trailing bytes) is shard + // corruption: report every block as mismatched so the caller treats the + // shard as bad. + want := unpackUint32LE(entry.BlockCrc32C) + all := make([]int, len(want)) + for i := range all { + all[i] = i + } + return all, nil + } + want := unpackUint32LE(entry.BlockCrc32C) + buf := make([]byte, blockSize) + var offset int64 + for i := 0; i < len(want); i++ { + toRead := blockSize + if rem := entry.CoveredSize - offset; rem < toRead { + toRead = rem + } + // os.File.ReadAt fills the slice or returns an error; a full read that + // lands exactly on EOF may still report io.EOF, which is not corruption. + // A short read (n < toRead) means the shard is truncated and the EOF is + // propagated as an error. + n, rerr := f.ReadAt(buf[:toRead], offset) + if rerr == io.EOF && int64(n) == toRead { + rerr = nil + } + if rerr != nil { + return nil, rerr + } + if uint32(needle.NewCRC(buf[:n])) != want[i] { + mismatched = append(mismatched, i) + } + offset += int64(n) + } + return mismatched, nil +} + +// ComputeProtectionFromShards builds a complete EcBitrotProtection by reading +// the on-disk bytes of every shard for a generation — the trust-on-first-use +// backfill primitive for EC volumes that were encoded before this feature. It +// requires EVERY shard to be reachable (locally or in additionalDirs) and +// returns an error otherwise, so a partial sidecar is never produced. This is +// the per-holder building block a coordinator assembles across servers; on a +// server that holds (or can reach) all shards it yields a complete manifest +// directly. Caveat: it blesses whatever bytes exist now and cannot detect +// pre-baseline corruption. +func ComputeProtectionFromShards(baseFileName string, ctx *ECContext, generation uint32, additionalDirs []string) (*volume_server_pb.EcBitrotProtection, error) { + shards := make([]*volume_server_pb.EcShardChecksums, 0, ctx.Total()) + for id := 0; id < ctx.Total(); id++ { + path := findShardFile(baseFileName, ctx.ToExt(id), additionalDirs) + if path == "" { + return nil, fmt.Errorf("bitrot backfill: shard %d missing for %s; refusing to write a partial sidecar", id, baseFileName) + } + covered, packed, err := computeShardFileCRCs(path, BitrotBlockSize) + if err != nil { + return nil, fmt.Errorf("bitrot backfill: read shard %d (%s): %w", id, path, err) + } + shards = append(shards, &volume_server_pb.EcShardChecksums{ + ShardId: uint32(id), + CoveredSize: covered, + BlockCrc32C: packed, + }) + } + return &volume_server_pb.EcBitrotProtection{ + Algorithm: volume_server_pb.ChecksumAlgorithm_CHECKSUM_CRC32C, + BlockSize: uint32(BitrotBlockSize), + Generation: generation, + EcShardConfig: &volume_server_pb.EcShardConfig{ + DataShards: uint32(ctx.DataShards), + ParityShards: uint32(ctx.ParityShards), + }, + Shards: shards, + EncodeUuid: NewEncodeUUID(), + }, nil +} + +// computeShardFileCRCs reads a shard file sequentially and returns its size and +// packed per-block CRC32C array. +func computeShardFileCRCs(path string, blockSize int64) (coveredSize int64, packed []byte, err error) { + f, err := os.Open(path) + if err != nil { + return 0, nil, err + } + defer f.Close() + b := newShardChecksumBuilder(blockSize) + buf := make([]byte, blockSize) + for { + n, rerr := f.Read(buf) + if n > 0 { + b.write(buf[:n]) + } + if rerr == io.EOF { + break + } + if rerr != nil { + return 0, nil, rerr + } + if n == 0 { + break + } + } + coveredSize, packed = b.finalize() + return coveredSize, packed, nil +} + +// BitrotProtection returns the active-generation bitrot protection and its +// status. The returned message is read-only; callers must not mutate it. +func (ev *EcVolume) BitrotProtection() (*volume_server_pb.EcBitrotProtection, BitrotStatus) { + ev.bitrotLock.RLock() + defer ev.bitrotLock.RUnlock() + return ev.bitrot, ev.bitrotStatus +} + +// loadActiveBitrotSidecar loads the generation-0 checksum sidecar into the +// volume. Best-effort: any failure leaves protection off/invalid without +// failing the mount. OSS only produces generation-0 (fresh-encode) sidecars. +func (ev *EcVolume) loadActiveBitrotSidecar() { + ev.loadBitrotForGeneration(0) +} + +// loadBitrotForGeneration loads and validates the sidecar describing generation +// `generation`, setting ev.bitrot/ev.bitrotStatus. Called at mount. Absent or +// generation/config-mismatched => BitrotOff; self-integrity/manifest failure => +// BitrotInvalid; usable => BitrotOn. +func (ev *EcVolume) loadBitrotForGeneration(generation uint32) { + ev.bitrotLock.Lock() + defer ev.bitrotLock.Unlock() + ev.bitrot = nil + ev.bitrotStatus = BitrotOff + + if ev.ECContext == nil { + return + } + path := findBitrotSidecar(generation, ev.DataBaseFileName(), ev.IndexBaseFileName()) + if path == "" { + return + } + prot, err := LoadBitrotSidecar(path) + if err != nil { + glog.Warningf("ec volume %d: bitrot sidecar %s self-integrity failed: %v", ev.VolumeId, path, err) + ev.bitrotStatus = BitrotInvalid + return + } + if prot.Generation != generation { + return // not for this generation -> off, not corruption + } + if prot.EcShardConfig == nil || + int(prot.EcShardConfig.DataShards) != ev.ECContext.DataShards || + int(prot.EcShardConfig.ParityShards) != ev.ECContext.ParityShards { + return + } + if err := ValidateBitrotManifest(prot, ev.ECContext.DataShards, ev.ECContext.ParityShards); err != nil { + glog.Warningf("ec volume %d: bitrot sidecar %s manifest invalid: %v", ev.VolumeId, path, err) + ev.bitrotStatus = BitrotInvalid + return + } + ev.bitrot = prot + ev.bitrotStatus = BitrotOn + glog.V(1).Infof("ec volume %d: loaded bitrot protection generation %d (%d shards, block_size %d)", + ev.VolumeId, generation, len(prot.Shards), prot.BlockSize) +} + +// RemoveBitrotSidecars removes the legacy .ecsum and any versioned +// .ecsum.v sidecars for a base file name. Best-effort. +func RemoveBitrotSidecars(base string) { + os.Remove(base + BitrotSidecarExt) + if matches, _ := filepath.Glob(base + BitrotSidecarExt + ".v*"); matches != nil { + for _, m := range matches { + os.Remove(m) + } + } +} + +// findBitrotSidecar resolves the sidecar path for a generation, searching the +// data base, the index base, and any additional directories — mirroring how +// shard/.vif lookups handle split data/idx layouts and per-disk mirrors. +func findBitrotSidecar(generation uint32, dataBase, indexBase string, additionalDirs ...string) string { + candidates := []string{ + BitrotSidecarPath(dataBase, generation), + BitrotSidecarPath(indexBase, generation), + } + base := filepath.Base(dataBase) + for _, dir := range additionalDirs { + candidates = append(candidates, BitrotSidecarPath(filepath.Join(dir, base), generation)) + } + for _, c := range candidates { + if c == "" { + continue + } + if _, err := os.Stat(c); err == nil { + return c + } + } + return "" +} diff --git a/weed/storage/erasure_coding/ec_bitrot_test.go b/weed/storage/erasure_coding/ec_bitrot_test.go new file mode 100644 index 000000000..62b6504d7 --- /dev/null +++ b/weed/storage/erasure_coding/ec_bitrot_test.go @@ -0,0 +1,362 @@ +package erasure_coding + +import ( + "math/rand" + "os" + "path/filepath" + "slices" + "testing" + + "github.com/seaweedfs/seaweedfs/weed/pb/volume_server_pb" + "github.com/seaweedfs/seaweedfs/weed/storage/needle" +) + +func TestBitrotSidecarRoundTripAndSelfIntegrity(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "vol.ecsum") + prot := &volume_server_pb.EcBitrotProtection{ + Algorithm: volume_server_pb.ChecksumAlgorithm_CHECKSUM_CRC32C, + BlockSize: DefaultBitrotBlockSize, + Generation: 0, + EcShardConfig: &volume_server_pb.EcShardConfig{DataShards: 10, ParityShards: 4}, + Shards: []*volume_server_pb.EcShardChecksums{ + {ShardId: 0, CoveredSize: 5, BlockCrc32C: packUint32LE([]uint32{123})}, + }, + EncodeUuid: NewEncodeUUID(), + } + if err := SaveBitrotSidecar(path, prot); err != nil { + t.Fatalf("save: %v", err) + } + got, err := LoadBitrotSidecar(path) + if err != nil { + t.Fatalf("load: %v", err) + } + if got.BlockSize != prot.BlockSize || len(got.Shards) != 1 || got.Shards[0].CoveredSize != 5 { + t.Fatalf("roundtrip mismatch: %+v", got) + } + + // Corrupt a payload byte and confirm the self-integrity check trips. + data, _ := os.ReadFile(path) + data[bitrotHeaderSize+2] ^= 0xff + if err := os.WriteFile(path, data, 0644); err != nil { + t.Fatal(err) + } + if _, err := LoadBitrotSidecar(path); err == nil { + t.Fatalf("expected self-integrity failure after payload corruption") + } +} + +func TestShardChecksumBuilderCrossesBoundaries(t *testing.T) { + blockSize := int64(1 << 20) + data := make([]byte, 0, 3*blockSize+12345) + r := rand.New(rand.NewSource(1)) + data = data[:cap(data)] + r.Read(data) + + // Feed in odd-sized chunks that cross block boundaries. + b := newShardChecksumBuilder(blockSize) + for off := 0; off < len(data); off += 7777 { + end := off + 7777 + if end > len(data) { + end = len(data) + } + b.write(data[off:end]) + } + covered, packed := b.finalize() + if covered != int64(len(data)) { + t.Fatalf("covered=%d want=%d", covered, len(data)) + } + got := unpackUint32LE(packed) + + // Compare to a direct per-block CRC. + want := []uint32{} + for off := int64(0); off < int64(len(data)); off += blockSize { + end := off + blockSize + if end > int64(len(data)) { + end = int64(len(data)) + } + want = append(want, uint32(needle.NewCRC(data[off:end]))) + } + if len(got) != len(want) { + t.Fatalf("block count got=%d want=%d", len(got), len(want)) + } + for i := range want { + if got[i] != want[i] { + t.Fatalf("block %d crc mismatch got=%x want=%x", i, got[i], want[i]) + } + } +} + +func TestValidateBitrotManifest(t *testing.T) { + mk := func(mut func(*volume_server_pb.EcBitrotProtection)) *volume_server_pb.EcBitrotProtection { + p := &volume_server_pb.EcBitrotProtection{ + Algorithm: volume_server_pb.ChecksumAlgorithm_CHECKSUM_CRC32C, + BlockSize: DefaultBitrotBlockSize, + EcShardConfig: &volume_server_pb.EcShardConfig{DataShards: 2, ParityShards: 1}, + } + for id := 0; id < 3; id++ { + p.Shards = append(p.Shards, &volume_server_pb.EcShardChecksums{ + ShardId: uint32(id), CoveredSize: 5, BlockCrc32C: packUint32LE([]uint32{1}), + }) + } + if mut != nil { + mut(p) + } + return p + } + if err := ValidateBitrotManifest(mk(nil), 2, 1); err != nil { + t.Fatalf("valid manifest rejected: %v", err) + } + bad := map[string]func(*volume_server_pb.EcBitrotProtection){ + "missing shard": func(p *volume_server_pb.EcBitrotProtection) { p.Shards = p.Shards[:2] }, + "out of range id": func(p *volume_server_pb.EcBitrotProtection) { p.Shards[2].ShardId = 9 }, + "duplicate id": func(p *volume_server_pb.EcBitrotProtection) { p.Shards[2].ShardId = 0 }, + "zero covered": func(p *volume_server_pb.EcBitrotProtection) { p.Shards[0].CoveredSize = 0 }, + "bad crc count": func(p *volume_server_pb.EcBitrotProtection) { p.Shards[0].BlockCrc32C = []byte{1, 2, 3} }, + "bad block size": func(p *volume_server_pb.EcBitrotProtection) { p.BlockSize = 3 << 20 }, // not power of two + "bad algorithm": func(p *volume_server_pb.EcBitrotProtection) { + p.Algorithm = volume_server_pb.ChecksumAlgorithm_CHECKSUM_NONE + }, + } + for name, mut := range bad { + if err := ValidateBitrotManifest(mk(mut), 2, 1); err == nil { + t.Errorf("%s: expected validation error, got nil", name) + } + } +} + +// writeRandomDat creates a .dat of n bytes for encoding. +func writeRandomDat(t *testing.T, base string, n int) { + t.Helper() + data := make([]byte, n) + rand.New(rand.NewSource(42)).Read(data) + if err := os.WriteFile(base+".dat", data, 0644); err != nil { + t.Fatal(err) + } +} + +func TestEncodeProducesVerifiableSidecar(t *testing.T) { + old := BitrotBlockSize + BitrotBlockSize = 1 << 20 + t.Cleanup(func() { BitrotBlockSize = old }) + + dir := t.TempDir() + base := filepath.Join(dir, "7") + writeRandomDat(t, base, 6*1024*1024) // ~6 MiB + + ctx := &ECContext{DataShards: 10, ParityShards: 4} + prot, err := WriteEcFiles(base, ctx) + if err != nil { + t.Fatalf("encode: %v", err) + } + if err := SaveBitrotSidecar(BitrotSidecarPath(base, 0), prot); err != nil { + t.Fatalf("save sidecar: %v", err) + } + if err := ValidateBitrotManifest(prot, 10, 4); err != nil { + t.Fatalf("encode produced invalid manifest: %v", err) + } + + // Every shard file verifies clean against the sidecar right after encode. + for id := 0; id < ctx.Total(); id++ { + entry := shardChecksums(prot, uint32(id)) + mism, verr := verifyShardFileBlocks(base+ctx.ToExt(id), entry, int64(prot.BlockSize)) + if verr != nil { + t.Fatalf("verify shard %d: %v", id, verr) + } + if len(mism) != 0 { + t.Fatalf("shard %d unexpectedly mismatched at encode time: %v", id, mism) + } + } + + // Corrupt one byte in shard 12 (a parity shard) and confirm detection. + corruptOneByte(t, base+ctx.ToExt(12)) + entry := shardChecksums(prot, 12) + mism, verr := verifyShardFileBlocks(base+ctx.ToExt(12), entry, int64(prot.BlockSize)) + if verr != nil { + t.Fatalf("verify corrupt shard: %v", verr) + } + if len(mism) == 0 { + t.Fatalf("expected to detect corruption in parity shard 12") + } +} + +func TestRebuildExcludesCorruptPresentShard(t *testing.T) { + old := BitrotBlockSize + BitrotBlockSize = 1 << 20 + t.Cleanup(func() { BitrotBlockSize = old }) + + dir := t.TempDir() + base := filepath.Join(dir, "9") + writeRandomDat(t, base, 6*1024*1024) + + ctx := &ECContext{DataShards: 10, ParityShards: 4} + prot, err := WriteEcFiles(base, ctx) + if err != nil { + t.Fatalf("encode: %v", err) + } + if err := SaveBitrotSidecar(BitrotSidecarPath(base, 0), prot); err != nil { + t.Fatalf("save sidecar: %v", err) + } + + // Snapshot a good copy of shard 3, then delete shard 0 (missing) and corrupt + // shard 3 (present-but-corrupt). Rebuild must regenerate both correctly. + good3, _ := os.ReadFile(base + ctx.ToExt(3)) + if err := os.Remove(base + ctx.ToExt(0)); err != nil { + t.Fatal(err) + } + corruptOneByte(t, base+ctx.ToExt(3)) + + generated, err := RebuildEcFiles(base, ctx, false) + if err != nil { + t.Fatalf("rebuild: %v", err) + } + if !slices.Contains(generated, 0) || !slices.Contains(generated, 3) { + t.Fatalf("expected shards 0 and 3 regenerated, got %v", generated) + } + + // Shard 3 should now byte-match its original good content, and every shard + // verifies clean against the sidecar. + now3, _ := os.ReadFile(base + ctx.ToExt(3)) + if string(now3) != string(good3) { + t.Fatalf("rebuilt shard 3 does not match original good content") + } + for id := 0; id < ctx.Total(); id++ { + entry := shardChecksums(prot, uint32(id)) + mism, verr := verifyShardFileBlocks(base+ctx.ToExt(id), entry, int64(prot.BlockSize)) + if verr != nil || len(mism) != 0 { + t.Fatalf("post-rebuild shard %d not clean: mism=%v err=%v", id, mism, verr) + } + } +} + +func TestComputeProtectionFromShardsBackfill(t *testing.T) { + old := BitrotBlockSize + BitrotBlockSize = 1 << 20 + t.Cleanup(func() { BitrotBlockSize = old }) + + dir := t.TempDir() + base := filepath.Join(dir, "11") + writeRandomDat(t, base, 6*1024*1024) + ctx := &ECContext{DataShards: 10, ParityShards: 4} + if _, err := WriteEcFiles(base, ctx); err != nil { + t.Fatalf("encode: %v", err) + } + + // Backfill from the on-disk shards (no encode pass) yields a complete, + // valid manifest that every shard verifies clean against. + prot, err := ComputeProtectionFromShards(base, ctx, 0, nil) + if err != nil { + t.Fatalf("backfill: %v", err) + } + if err := ValidateBitrotManifest(prot, 10, 4); err != nil { + t.Fatalf("backfill manifest invalid: %v", err) + } + for id := 0; id < ctx.Total(); id++ { + entry := shardChecksums(prot, uint32(id)) + mism, verr := verifyShardFileBlocks(base+ctx.ToExt(id), entry, int64(prot.BlockSize)) + if verr != nil || len(mism) != 0 { + t.Fatalf("backfilled shard %d not clean: mism=%v err=%v", id, mism, verr) + } + } + + // A missing shard must abort backfill (never write a partial manifest). + if err := os.Remove(base + ctx.ToExt(5)); err != nil { + t.Fatal(err) + } + if _, err := ComputeProtectionFromShards(base, ctx, 0, nil); err == nil { + t.Fatalf("expected backfill error with a missing shard") + } +} + +func TestRebuildFailClosedCleansUpGeneratedShard(t *testing.T) { + old := BitrotBlockSize + BitrotBlockSize = 1 << 20 + t.Cleanup(func() { BitrotBlockSize = old }) + + dir := t.TempDir() + base := filepath.Join(dir, "13") + writeRandomDat(t, base, 6*1024*1024) + ctx := &ECContext{DataShards: 10, ParityShards: 4} + prot, err := WriteEcFiles(base, ctx) + if err != nil { + t.Fatalf("encode: %v", err) + } + + // Tamper one block CRC of shard 0 but re-save so the header self-integrity and + // manifest stay valid: a well-formed but wrong ("stale") sidecar that loads as ON. + for _, s := range prot.Shards { + if s.ShardId == 0 && len(s.BlockCrc32C) >= 1 { + s.BlockCrc32C[0] ^= 0xff + } + } + if err := SaveBitrotSidecar(BitrotSidecarPath(base, 0), prot); err != nil { + t.Fatalf("save sidecar: %v", err) + } + + // Delete shard 0 so the rebuild regenerates it; the regenerated (correct) + // bytes will not match the tampered sidecar entry -> post-verify fails closed. + shard0 := base + ctx.ToExt(0) + if err := os.Remove(shard0); err != nil { + t.Fatal(err) + } + + if _, rerr := RebuildEcFiles(base, ctx, false); rerr == nil { + t.Fatalf("expected rebuild to fail closed on the stale sidecar") + } + // The genuinely-missing shard 0 must NOT be published: fail-closed cleanup + // returns it to missing. + if _, err := os.Stat(shard0); !os.IsNotExist(err) { + t.Fatalf("shard 0 should have been cleaned up after a failed rebuild, err=%v", err) + } + + // With the unsafe override, the rebuild proceeds and regenerates shard 0. + if _, rerr := RebuildEcFiles(base, ctx, true); rerr != nil { + t.Fatalf("rebuild with unsafeIgnoreSidecar should succeed: %v", rerr) + } + if _, err := os.Stat(shard0); err != nil { + t.Fatalf("shard 0 should exist after unsafe rebuild: %v", err) + } +} + +func TestRemoveBitrotSidecars(t *testing.T) { + dir := t.TempDir() + base := filepath.Join(dir, "5") + // Legacy + two versioned sidecars, plus an unrelated file that must survive. + for _, p := range []string{base + ".ecsum", base + ".ecsum.v1", base + ".ecsum.v7"} { + if err := os.WriteFile(p, []byte("x"), 0644); err != nil { + t.Fatal(err) + } + } + keep := base + ".ec00" + if err := os.WriteFile(keep, []byte("x"), 0644); err != nil { + t.Fatal(err) + } + + RemoveBitrotSidecars(base) + + for _, p := range []string{base + ".ecsum", base + ".ecsum.v1", base + ".ecsum.v7"} { + if _, err := os.Stat(p); !os.IsNotExist(err) { + t.Errorf("expected %s removed, err=%v", p, err) + } + } + if _, err := os.Stat(keep); err != nil { + t.Errorf("unrelated file %s should survive: %v", keep, err) + } +} + +func corruptOneByte(t *testing.T, path string) { + t.Helper() + f, err := os.OpenFile(path, os.O_RDWR, 0644) + if err != nil { + t.Fatal(err) + } + defer f.Close() + var b [1]byte + if _, err := f.ReadAt(b[:], 0); err != nil { + t.Fatal(err) + } + b[0] ^= 0xff + if _, err := f.WriteAt(b[:], 0); err != nil { + t.Fatal(err) + } +} diff --git a/weed/storage/erasure_coding/ec_consistency_test.go b/weed/storage/erasure_coding/ec_consistency_test.go index ec6e7d632..da1a26cc7 100644 --- a/weed/storage/erasure_coding/ec_consistency_test.go +++ b/weed/storage/erasure_coding/ec_consistency_test.go @@ -18,7 +18,7 @@ import ( // existed in VolumeEcShardsGenerate before the fix in this PR. // // Previously, the order was: -// 1. WriteEcFilesWithContext(baseFileName, ecCtx) — EC shards from .dat +// 1. WriteEcFiles(baseFileName, ecCtx) — EC shards from .dat // 2. WriteSortedFileFromIdx(v.IndexFileName(), ".ecx") — .ecx from .idx // // If a write appended data to .dat/.idx between steps 1 and 2, the .ecx would @@ -48,7 +48,7 @@ func TestEcConsistency_WritesBetweenEncodeAndEcx(t *testing.T) { }) // Phase 2: EC encode — generates .ec00-.ec13 from current .dat - err = generateEcFiles(baseFileName, int(smallBlockSize), largeBlockSize, smallBlockSize, ctx) + _, err = generateEcFiles(baseFileName, int(smallBlockSize), largeBlockSize, smallBlockSize, ctx) require.NoError(t, err, "EC encoding") // Phase 3: SIMULATE a write between EC encoding and .ecx generation @@ -145,7 +145,7 @@ func TestEcConsistency_ExactLargeRowEncoding(t *testing.T) { require.NoError(t, err) // EC encode - err = generateEcFiles(baseFileName, int(smallBlockSize), largeBlockSize, smallBlockSize, ctx) + _, err = generateEcFiles(baseFileName, int(smallBlockSize), largeBlockSize, smallBlockSize, ctx) require.NoError(t, err) // Check shard sizes — each shard should be exactly largeBlockSize diff --git a/weed/storage/erasure_coding/ec_context.go b/weed/storage/erasure_coding/ec_context.go index 770fe41af..9fc352d83 100644 --- a/weed/storage/erasure_coding/ec_context.go +++ b/weed/storage/erasure_coding/ec_context.go @@ -30,6 +30,17 @@ func NewDefaultECContext(collection string, volumeId needle.VolumeId) *ECContext } } +// BackgroundECContext returns a non-nil placeholder EC context, analogous to +// context.Background(): pass it to WriteEcFiles / RebuildEcFiles when the caller +// has no specific layout, rather than a nil context. Its zero Total() is the +// "unset" signal — WriteEcFiles resolves it to the default ratio and +// RebuildEcFiles resolves it from the volume's .vif (falling back to default), +// so the placeholder itself never reaches the encoder. A fresh value is returned +// each call so callers cannot mutate a shared default. +func BackgroundECContext() *ECContext { + return &ECContext{} +} + // CreateEncoder creates a Reed-Solomon encoder for this context func (ctx *ECContext) CreateEncoder() (reedsolomon.Encoder, error) { return reedsolomon.New(ctx.DataShards, ctx.ParityShards) diff --git a/weed/storage/erasure_coding/ec_encoder.go b/weed/storage/erasure_coding/ec_encoder.go index cd4d95fd8..b0e689eb4 100644 --- a/weed/storage/erasure_coding/ec_encoder.go +++ b/weed/storage/erasure_coding/ec_encoder.go @@ -9,6 +9,7 @@ import ( "github.com/klauspost/reedsolomon" "github.com/seaweedfs/seaweedfs/weed/glog" + "github.com/seaweedfs/seaweedfs/weed/pb/volume_server_pb" "github.com/seaweedfs/seaweedfs/weed/storage/idx" "github.com/seaweedfs/seaweedfs/weed/storage/needle_map" "github.com/seaweedfs/seaweedfs/weed/storage/types" @@ -57,74 +58,89 @@ func WriteSortedFileFromIdx(baseFileName string, ext string) (e error) { return nil } -// WriteEcFiles generates .ec00 ~ .ec13 files using default EC context -func WriteEcFiles(baseFileName string) error { - ctx := NewDefaultECContext("", 0) - return WriteEcFilesWithContext(baseFileName, ctx) -} - -// WriteEcFilesWithContext generates EC files using the provided context -func WriteEcFilesWithContext(baseFileName string, ctx *ECContext) error { +// WriteEcFiles generates .ec00 ~ .ec13 files from baseFileName.dat. Pass +// BackgroundECContext for the default ratio, or an explicit ctx for a configured +// (e.g. custom-ratio) layout. It returns the bitrot protection (per-shard block +// CRC32C) computed during the single encode pass; the caller persists it as a +// .ecsum sidecar. +func WriteEcFiles(baseFileName string, ctx *ECContext) (*volume_server_pb.EcBitrotProtection, error) { + if ctx == nil || ctx.Total() == 0 { + ctx = NewDefaultECContext("", 0) + } return generateEcFiles(baseFileName, 256*1024, ErasureCodingLargeBlockSize, ErasureCodingSmallBlockSize, ctx) } -// RebuildEcFiles rebuilds missing EC shard files. +// RebuildEcFiles rebuilds missing EC shard files. Pass BackgroundECContext to +// resolve the layout from the volume's .vif (falling back to the default ratio), +// or an explicit ctx when the caller already knows the shard layout. // additionalDirs are extra directories to search for existing shard files, // which handles multi-disk servers where shards may be spread across disks. -func RebuildEcFiles(baseFileName string, additionalDirs ...string) ([]uint32, error) { - // Attempt to load EC config from .vif file to preserve original configuration - var ctx *ECContext - if volumeInfo, _, found, _ := volume_info.MaybeLoadVolumeInfo(baseFileName + ".vif"); found && volumeInfo.EcShardConfig != nil { - ds := int(volumeInfo.EcShardConfig.DataShards) - ps := int(volumeInfo.EcShardConfig.ParityShards) +// When a bitrot checksum sidecar is present for the (generation-0) volume, +// present input shards are verified against it and corrupt ones are excluded +// from Reed-Solomon and regenerated; unsafeIgnoreSidecar bypasses that guard. +func RebuildEcFiles(baseFileName string, ctx *ECContext, unsafeIgnoreSidecar bool, additionalDirs ...string) ([]uint32, error) { + if ctx == nil || ctx.Total() == 0 { + // Resolve the layout from the .vif to preserve the original configuration. + volumeInfo, _, foundVif, vifErr := volume_info.MaybeLoadVolumeInfo(baseFileName + ".vif") + if vifErr != nil { + // The .vif exists but cannot be read or parsed. Fail closed rather + // than silently falling back to the default ratio, which would + // rebuild a custom-ratio volume with the wrong layout. Pass an + // explicit ctx to override. + return nil, fmt.Errorf("RebuildEcFiles %s: cannot load .vif: %w", baseFileName, vifErr) + } + if foundVif && volumeInfo.EcShardConfig != nil { + ds := int(volumeInfo.EcShardConfig.DataShards) + ps := int(volumeInfo.EcShardConfig.ParityShards) - // Validate EC config before using it - if ds > 0 && ps > 0 && ds+ps <= MaxShardCount { - ctx = &ECContext{ - DataShards: ds, - ParityShards: ps, + // Validate EC config before using it + if ds > 0 && ps > 0 && ds+ps <= MaxShardCount { + ctx = &ECContext{ + DataShards: ds, + ParityShards: ps, + } + glog.V(0).Infof("Rebuilding EC files for %s with config from .vif: %s", baseFileName, ctx.String()) + } else { + glog.Warningf("Invalid EC config in .vif for %s (data=%d, parity=%d), using default", baseFileName, ds, ps) + ctx = NewDefaultECContext("", 0) } - glog.V(0).Infof("Rebuilding EC files for %s with config from .vif: %s", baseFileName, ctx.String()) } else { - glog.Warningf("Invalid EC config in .vif for %s (data=%d, parity=%d), using default", baseFileName, ds, ps) + glog.V(0).Infof("Rebuilding EC files for %s with default config", baseFileName) ctx = NewDefaultECContext("", 0) } - } else { - glog.V(0).Infof("Rebuilding EC files for %s with default config", baseFileName) - ctx = NewDefaultECContext("", 0) } - return RebuildEcFilesWithContext(baseFileName, ctx, additionalDirs...) -} - -// RebuildEcFilesWithContext rebuilds missing EC files using the provided context. -// additionalDirs are extra directories to search for existing shard files. -func RebuildEcFilesWithContext(baseFileName string, ctx *ECContext, additionalDirs ...string) ([]uint32, error) { - return generateMissingEcFiles(baseFileName, 256*1024, ErasureCodingLargeBlockSize, ErasureCodingSmallBlockSize, ctx, additionalDirs) + return generateMissingEcFiles(baseFileName, 256*1024, ErasureCodingLargeBlockSize, ErasureCodingSmallBlockSize, ctx, unsafeIgnoreSidecar, additionalDirs) } func ToExt(ecIndex int) string { return fmt.Sprintf(".ec%02d", ecIndex) } -func generateEcFiles(baseFileName string, bufferSize int, largeBlockSize int64, smallBlockSize int64, ctx *ECContext) error { +func generateEcFiles(baseFileName string, bufferSize int, largeBlockSize int64, smallBlockSize int64, ctx *ECContext) (*volume_server_pb.EcBitrotProtection, error) { file, err := os.OpenFile(baseFileName+".dat", os.O_RDONLY, 0) if err != nil { - return fmt.Errorf("failed to open dat file: %w", err) + return nil, fmt.Errorf("failed to open dat file: %w", err) } defer file.Close() fi, err := file.Stat() if err != nil { - return fmt.Errorf("failed to stat dat file: %w", err) + return nil, fmt.Errorf("failed to stat dat file: %w", err) + } + + // One rolling-CRC builder per shard; fed as each shard's bytes are written. + builders := make([]*shardChecksumBuilder, ctx.Total()) + for i := range builders { + builders[i] = newShardChecksumBuilder(BitrotBlockSize) } glog.V(0).Infof("encodeDatFile %s.dat size:%d with EC context %s", baseFileName, fi.Size(), ctx.String()) - err = encodeDatFile(fi.Size(), baseFileName, bufferSize, largeBlockSize, file, smallBlockSize, ctx) + err = encodeDatFile(fi.Size(), baseFileName, bufferSize, largeBlockSize, file, smallBlockSize, ctx, builders) if err != nil { - return fmt.Errorf("encodeDatFile: %w", err) + return nil, fmt.Errorf("encodeDatFile: %w", err) } - return nil + return buildProtectionFromBuilders(ctx, builders, BitrotBlockSize), nil } // findShardFile looks for a shard file at baseFileName+ext, then in additionalDirs. @@ -143,12 +159,12 @@ func findShardFile(baseFileName string, ext string, additionalDirs []string) str return "" } -func generateMissingEcFiles(baseFileName string, bufferSize int, largeBlockSize int64, smallBlockSize int64, ctx *ECContext, additionalDirs []string) (generatedShardIds []uint32, err error) { +func generateMissingEcFiles(baseFileName string, bufferSize int, largeBlockSize int64, smallBlockSize int64, ctx *ECContext, unsafeIgnoreSidecar bool, additionalDirs []string) (generatedShardIds []uint32, err error) { // Pass 1: discover which shards exist and which are missing, // opening input files but NOT creating output files yet. shardHasData := make([]bool, ctx.Total()) - shardPaths := make([]string, ctx.Total()) // non-empty for present shards + shardPaths := make([]string, ctx.Total()) // non-empty for present shards (also the in-place output for a reclassified-corrupt shard) inputFiles := make([]*os.File, ctx.Total()) presentCount := 0 for shardId := 0; shardId < ctx.Total(); shardId++ { @@ -168,6 +184,67 @@ func generateMissingEcFiles(baseFileName string, bufferSize int, largeBlockSize } } + // Bitrot verify-and-exclude: when a generation-0 checksum sidecar is present + // and valid, verify each present input shard against it and reclassify + // corrupt ones as missing so Reed-Solomon regenerates them instead of + // silently consuming corrupt bytes. corruptOwned marks shards whose + // (corrupt) original file must be replaced in place at its discovered path. + corruptOwned := make([]bool, ctx.Total()) + prot, status := loadRebuildSidecar(baseFileName, ctx, additionalDirs) + switch status { + case BitrotInvalid: + if !unsafeIgnoreSidecar { + return nil, fmt.Errorf("bitrot sidecar for %s is malformed/unverifiable; refusing to rebuild (pass unsafeIgnoreSidecar to override)", baseFileName) + } + glog.Warningf("bitrot sidecar for %s is malformed/unverifiable; proceeding because unsafeIgnoreSidecar is set", baseFileName) + case BitrotOn: + corrupt := make([]int, 0, ctx.Total()) + for shardId := 0; shardId < ctx.Total(); shardId++ { + if !shardHasData[shardId] { + continue + } + entry := shardChecksums(prot, uint32(shardId)) + if entry == nil { + continue + } + mismatched, verr := verifyShardFileBlocks(shardPaths[shardId], entry, int64(prot.BlockSize)) + if verr != nil { + // A read error means we cannot trust this shard as a Reed-Solomon + // input. Exclude it (treat as corrupt) rather than silently + // feeding possibly-corrupt bytes into reconstruction. + glog.Warningf("bitrot: failed to verify present shard %d for %s: %v; excluding it", shardId, baseFileName, verr) + corrupt = append(corrupt, shardId) + continue + } + if len(mismatched) > 0 { + corrupt = append(corrupt, shardId) + } + } + if len(corrupt) > 0 { + // Wholesale-mismatch guard (RS-arbiter conservative form): localized + // bitrot touches a few shards; a stale/wrong sidecar mismatches more + // than parity_shards. In that case refuse rather than excluding good + // shards en masse. + if len(corrupt) > ctx.ParityShards && !unsafeIgnoreSidecar { + return nil, fmt.Errorf("bitrot sidecar suspect for %s: %d/%d present shards mismatch (> parity %d); refusing to rebuild (pass unsafeIgnoreSidecar to override)", + baseFileName, len(corrupt), presentCount, ctx.ParityShards) + } + if presentCount-len(corrupt) < ctx.DataShards && !unsafeIgnoreSidecar { + return nil, fmt.Errorf("bitrot: only %d verified-good shards for %s, need %d data shards; sidecar may be stale (pass unsafeIgnoreSidecar to override)", + presentCount-len(corrupt), baseFileName, ctx.DataShards) + } + if !unsafeIgnoreSidecar { + for _, shardId := range corrupt { + glog.Warningf("bitrot: present shard %d for %s fails checksum; excluding from rebuild inputs and regenerating", shardId, baseFileName) + shardHasData[shardId] = false + corruptOwned[shardId] = true + generatedShardIds = append(generatedShardIds, uint32(shardId)) + presentCount-- + } + } + } + } + // Pre-check: bail out before creating any output files. if presentCount < ctx.DataShards { return nil, fmt.Errorf("not enough shards to rebuild %s: found %d shards, need at least %d (data shards), missing shards: %v", @@ -177,29 +254,131 @@ func generateMissingEcFiles(baseFileName string, bufferSize int, largeBlockSize glog.V(0).Infof("rebuilding %s: %d shards present, %d missing %v, config %s", baseFileName, presentCount, len(generatedShardIds), generatedShardIds, ctx.String()) - // Pass 2: create output files for missing shards now that we know - // reconstruction is possible. + // Pass 2: create output files for missing shards. A genuinely-absent shard + // is written at baseFileName+ext; a reclassified-corrupt shard is written to + // a temp file beside its discovered location and atomically renamed over the + // corrupt original after the rebuild (and checksum) succeed, so we never + // leave a duplicate shard id or a half-written file. outputFiles := make([]*os.File, ctx.Total()) + writePaths := make([]string, ctx.Total()) + finalPaths := make([]string, ctx.Total()) for shardId := 0; shardId < ctx.Total(); shardId++ { if shardHasData[shardId] { continue } - outputFileName := baseFileName + ctx.ToExt(shardId) - outputFiles[shardId], err = os.OpenFile(outputFileName, os.O_TRUNC|os.O_WRONLY|os.O_CREATE, 0644) + finalPath := baseFileName + ctx.ToExt(shardId) + writePath := finalPath + if corruptOwned[shardId] && shardPaths[shardId] != "" { + finalPath = shardPaths[shardId] + writePath = shardPaths[shardId] + ".rebuilding" + } + outputFiles[shardId], err = os.OpenFile(writePath, os.O_TRUNC|os.O_WRONLY|os.O_CREATE, 0644) if err != nil { return nil, err } defer outputFiles[shardId].Close() + writePaths[shardId] = writePath + finalPaths[shardId] = finalPath } - err = rebuildEcFiles(shardHasData, inputFiles, outputFiles, ctx) - if err != nil { + if err = rebuildEcFiles(shardHasData, inputFiles, outputFiles, ctx); err != nil { return nil, fmt.Errorf("rebuildEcFiles: %w", err) } + + // Verify regenerated shards against the sidecar. Reed-Solomon is + // deterministic, so a regenerated shard that does NOT match the sidecar + // means the sidecar is wrong/stale (not the shard) — fail closed rather than + // publishing bytes we cannot trust. On ANY verification failure (sync, read + // error, or mismatch) remove every generated output so the rebuild publishes + // nothing: a genuinely-missing shard returns to missing; a reclassified- + // corrupt shard keeps its untouched original. + if status == BitrotOn && !unsafeIgnoreSidecar { + for shardId := 0; shardId < ctx.Total(); shardId++ { + if writePaths[shardId] == "" { + continue + } + entry := shardChecksums(prot, uint32(shardId)) + if entry == nil { + continue + } + if err = outputFiles[shardId].Sync(); err != nil { + cleanupRebuildOutputs(outputFiles, writePaths) + return nil, fmt.Errorf("sync regenerated shard %d: %w", shardId, err) + } + mismatched, verr := verifyShardFileBlocks(writePaths[shardId], entry, int64(prot.BlockSize)) + if verr != nil { + cleanupRebuildOutputs(outputFiles, writePaths) + return nil, fmt.Errorf("bitrot: verify regenerated shard %d for %s: %w", shardId, baseFileName, verr) + } + if len(mismatched) > 0 { + cleanupRebuildOutputs(outputFiles, writePaths) + return nil, fmt.Errorf("bitrot: regenerated shard %d for %s does not match sidecar (%d blocks differ); sidecar likely stale — aborting (pass unsafeIgnoreSidecar to override)", + shardId, baseFileName, len(mismatched)) + } + } + } + + // Atomically move reclassified-corrupt rebuilds over their originals. + for shardId := 0; shardId < ctx.Total(); shardId++ { + if writePaths[shardId] != "" && writePaths[shardId] != finalPaths[shardId] { + outputFiles[shardId].Close() + if rerr := os.Rename(writePaths[shardId], finalPaths[shardId]); rerr != nil { + return nil, fmt.Errorf("bitrot: replace corrupt shard %d (%s -> %s): %w", shardId, writePaths[shardId], finalPaths[shardId], rerr) + } + } + } return } -func encodeData(file *os.File, enc reedsolomon.Encoder, startOffset, blockSize int64, buffers [][]byte, outputs []*os.File, ctx *ECContext) error { +// cleanupRebuildOutputs removes every generated output on a failed fail-closed +// rebuild: temp replacements AND genuinely-missing shards written directly at +// their final path, so no unverified bytes are published. A reclassified-corrupt +// shard's untouched original (at finalPath, distinct from its temp writePath) is +// left in place; a genuinely-missing shard (writePath == finalPath) returns to +// missing. +func cleanupRebuildOutputs(outputFiles []*os.File, writePaths []string) { + for i := range writePaths { + if writePaths[i] == "" { + continue + } + if outputFiles[i] != nil { + outputFiles[i].Close() + } + os.Remove(writePaths[i]) + } +} + +// loadRebuildSidecar loads and validates the generation-0 checksum sidecar for a +// rebuild. RebuildEcFiles operates on the un-suffixed (generation 0) shard +// names, so only the legacy sidecar is relevant here. Returns BitrotOff when +// absent or describing a different generation/config, BitrotInvalid on a +// self-integrity/manifest failure, BitrotOn when usable. +func loadRebuildSidecar(baseFileName string, ctx *ECContext, additionalDirs []string) (*volume_server_pb.EcBitrotProtection, BitrotStatus) { + path := findBitrotSidecar(0, baseFileName, baseFileName, additionalDirs...) + if path == "" { + return nil, BitrotOff + } + prot, err := LoadBitrotSidecar(path) + if err != nil { + glog.Warningf("bitrot: sidecar %s self-integrity failed: %v", path, err) + return nil, BitrotInvalid + } + if prot.Generation != 0 { + return nil, BitrotOff + } + if prot.EcShardConfig == nil || + int(prot.EcShardConfig.DataShards) != ctx.DataShards || + int(prot.EcShardConfig.ParityShards) != ctx.ParityShards { + return nil, BitrotOff + } + if err := ValidateBitrotManifest(prot, ctx.DataShards, ctx.ParityShards); err != nil { + glog.Warningf("bitrot: sidecar %s manifest invalid: %v", path, err) + return nil, BitrotInvalid + } + return prot, BitrotOn +} + +func encodeData(file *os.File, enc reedsolomon.Encoder, startOffset, blockSize int64, buffers [][]byte, outputs []*os.File, ctx *ECContext, builders []*shardChecksumBuilder) error { bufferSize := int64(len(buffers[0])) if bufferSize == 0 { @@ -212,7 +391,7 @@ func encodeData(file *os.File, enc reedsolomon.Encoder, startOffset, blockSize i } for b := int64(0); b < batchCount; b++ { - err := encodeDataOneBatch(file, enc, startOffset+b*bufferSize, blockSize, buffers, outputs, ctx) + err := encodeDataOneBatch(file, enc, startOffset+b*bufferSize, blockSize, buffers, outputs, ctx, builders) if err != nil { return err } @@ -245,7 +424,7 @@ func closeEcFiles(files []*os.File) { } } -func encodeDataOneBatch(file *os.File, enc reedsolomon.Encoder, startOffset, blockSize int64, buffers [][]byte, outputs []*os.File, ctx *ECContext) error { +func encodeDataOneBatch(file *os.File, enc reedsolomon.Encoder, startOffset, blockSize int64, buffers [][]byte, outputs []*os.File, ctx *ECContext, builders []*shardChecksumBuilder) error { // read data into buffers for i := 0; i < ctx.DataShards; i++ { @@ -272,12 +451,16 @@ func encodeDataOneBatch(file *os.File, enc reedsolomon.Encoder, startOffset, blo if err != nil { return err } + // Accumulate this shard's block CRC over exactly the bytes written. + if builders != nil && builders[i] != nil { + builders[i].write(buffers[i]) + } } return nil } -func encodeDatFile(remainingSize int64, baseFileName string, bufferSize int, largeBlockSize int64, file *os.File, smallBlockSize int64, ctx *ECContext) error { +func encodeDatFile(remainingSize int64, baseFileName string, bufferSize int, largeBlockSize int64, file *os.File, smallBlockSize int64, ctx *ECContext, builders []*shardChecksumBuilder) error { var processedSize int64 @@ -302,7 +485,7 @@ func encodeDatFile(remainingSize int64, baseFileName string, bufferSize int, lar smallRowSize := smallBlockSize * int64(ctx.DataShards) for remainingSize >= largeRowSize { - err = encodeData(file, enc, processedSize, largeBlockSize, buffers, outputs, ctx) + err = encodeData(file, enc, processedSize, largeBlockSize, buffers, outputs, ctx, builders) if err != nil { return fmt.Errorf("failed to encode large chunk data: %w", err) } @@ -310,7 +493,7 @@ func encodeDatFile(remainingSize int64, baseFileName string, bufferSize int, lar processedSize += largeRowSize } for remainingSize > 0 { - err = encodeData(file, enc, processedSize, smallBlockSize, buffers, outputs, ctx) + err = encodeData(file, enc, processedSize, smallBlockSize, buffers, outputs, ctx, builders) if err != nil { return fmt.Errorf("failed to encode small chunk data: %w", err) } diff --git a/weed/storage/erasure_coding/ec_roundtrip_test.go b/weed/storage/erasure_coding/ec_roundtrip_test.go index 38fdfff16..0ba3fc990 100644 --- a/weed/storage/erasure_coding/ec_roundtrip_test.go +++ b/weed/storage/erasure_coding/ec_roundtrip_test.go @@ -81,7 +81,7 @@ func testEcRead(t *testing.T, large, small, datSize int64) { ctx := NewDefaultECContext("", 0) // 2. EC encode with test block sizes - err = generateEcFiles(baseFileName, int(small), large, small, ctx) + _, err = generateEcFiles(baseFileName, int(small), large, small, ctx) require.NoError(t, err, "EC encoding") // 3. Open EC shard files for reading @@ -213,7 +213,7 @@ func TestEcOffByOneBug_Issue8947(t *testing.T) { require.NoError(t, err) ctx := NewDefaultECContext("", 0) - err = generateEcFiles(baseFileName, int(small), large, small, ctx) + _, err = generateEcFiles(baseFileName, int(small), large, small, ctx) require.NoError(t, err, "EC encoding") ecFiles, err := openEcFiles(baseFileName, true, ctx) @@ -316,7 +316,7 @@ func testDecodeDat(t *testing.T, datSize int64) { ctx := NewDefaultECContext("", 0) // 2. EC encode with PRODUCTION block sizes - err = generateEcFiles(baseFileName, 256*1024, ErasureCodingLargeBlockSize, ErasureCodingSmallBlockSize, ctx) + _, err = generateEcFiles(baseFileName, 256*1024, ErasureCodingLargeBlockSize, ErasureCodingSmallBlockSize, ctx) require.NoError(t, err, "EC encoding") // 3. Decode via WriteDatFile diff --git a/weed/storage/erasure_coding/ec_test.go b/weed/storage/erasure_coding/ec_test.go index 3894d75d3..01189bf5e 100644 --- a/weed/storage/erasure_coding/ec_test.go +++ b/weed/storage/erasure_coding/ec_test.go @@ -27,7 +27,7 @@ func TestEncodingDecoding(t *testing.T) { // Create default EC context for testing ctx := NewDefaultECContext("", 0) - err := generateEcFiles(baseFileName, bufferSize, largeBlockSize, smallBlockSize, ctx) + _, err := generateEcFiles(baseFileName, bufferSize, largeBlockSize, smallBlockSize, ctx) if err != nil { t.Logf("generateEcFiles: %v", err) } @@ -194,6 +194,7 @@ func removeGeneratedFiles(baseFileName string, ctx *ECContext) { os.Remove(fname) } os.Remove(baseFileName + ".ecx") + RemoveBitrotSidecars(baseFileName) } func TestLocateData(t *testing.T) { diff --git a/weed/storage/erasure_coding/ec_volume.go b/weed/storage/erasure_coding/ec_volume.go index fd5e5b5db..4860ae2ce 100644 --- a/weed/storage/erasure_coding/ec_volume.go +++ b/weed/storage/erasure_coding/ec_volume.go @@ -59,6 +59,13 @@ type EcVolume struct { // Seeded from .ecj in NewEcVolume and updated under deletedNeedlesLock. deletedNeedlesLock sync.RWMutex deletedNeedles map[types.NeedleId]struct{} + + // Bitrot checksum sidecar for the active generation (optional). bitrot is + // nil unless bitrotStatus == BitrotOn, and is loaded at mount. Guarded by + // bitrotLock. + bitrotLock sync.RWMutex + bitrot *volume_server_pb.EcBitrotProtection + bitrotStatus BitrotStatus } func NewEcVolume(diskType types.DiskType, dir string, dirIdx string, collection string, vid needle.VolumeId) (ev *EcVolume, err error) { @@ -168,6 +175,9 @@ func NewEcVolume(diskType types.DiskType, dir string, dirIdx string, collection ev.ShardLocations = make(map[ShardId][]pb.ServerAddress) + // Load the active-generation bitrot checksum sidecar (optional). + ev.loadActiveBitrotSidecar() + return } @@ -257,6 +267,12 @@ func (ev *EcVolume) Destroy() { os.Remove(ev.FileName(".ecx")) os.Remove(ev.FileName(".ecj")) os.Remove(ev.FileName(".vif")) + // Remove the bitrot checksum sidecar(s) so a later volume reuse cannot load + // stale protection. Search both the data and index bases. + RemoveBitrotSidecars(ev.DataBaseFileName()) + if ev.IndexBaseFileName() != ev.DataBaseFileName() { + RemoveBitrotSidecars(ev.IndexBaseFileName()) + } } // DiskType returns the disk type the EC volume currently reports under. diff --git a/weed/storage/erasure_coding/ec_volume_scrub.go b/weed/storage/erasure_coding/ec_volume_scrub.go index a563875f7..aa759058b 100644 --- a/weed/storage/erasure_coding/ec_volume_scrub.go +++ b/weed/storage/erasure_coding/ec_volume_scrub.go @@ -1,6 +1,7 @@ package erasure_coding import ( + "bytes" "fmt" "slices" @@ -22,6 +23,190 @@ func (ev *EcVolume) ScrubIndex() (int64, []error) { return idx.CheckIndexFile(ev.ecxFile, ev.ecxFileSize, ev.Version) } +// ChecksumScrub verifies every locally-held EC shard's raw bytes against the +// active-generation bitrot checksum sidecar. It is read-only: it detects and +// reports corruption but never mutates or quarantines anything (the apply path +// drives quarantine + rebuild). It is the only path that exercises cold parity +// shards, which are never read during normal serving. +// +// Returns the number of blocks scanned, the broken shards (sidecar mismatch or +// length drift), and errors. A wholesale mismatch (more than parity_shards +// shards differing) is reported as a suspect/stale sidecar — an integrity error +// — rather than flagging the shards as corrupt, so a bad sidecar can never be +// mistaken for mass bitrot. +func (ecv *EcVolume) ChecksumScrub() (blocksScanned int64, brokenShards []*volume_server_pb.EcShardInfo, errs []error) { + prot, status := ecv.BitrotProtection() + switch status { + case BitrotOff: + // Unprotected generation: nothing to verify. Not an error. + return 0, nil, nil + case BitrotInvalid: + return 0, nil, []error{fmt.Errorf("ec volume %d: bitrot sidecar is malformed/unverifiable (sidecar integrity)", ecv.VolumeId)} + } + + blockSize := int64(prot.BlockSize) + var broken []*EcVolumeShard + for _, shard := range ecv.Shards { + entry := shardChecksums(prot, uint32(shard.ShardId)) + if entry == nil { + errs = append(errs, fmt.Errorf("ec volume %d: no checksum entry for local shard %d", ecv.VolumeId, shard.ShardId)) + continue + } + if shard.Size() != entry.CoveredSize { + errs = append(errs, fmt.Errorf("ec volume %d shard %d: size %d != covered_size %d (truncated or extended)", + ecv.VolumeId, shard.ShardId, shard.Size(), entry.CoveredSize)) + broken = append(broken, shard) + continue + } + want := unpackUint32LE(entry.BlockCrc32C) + buf := make([]byte, blockSize) + var offset int64 + shardBad := false + for i := 0; i < len(want); i++ { + toRead := blockSize + if rem := entry.CoveredSize - offset; rem < toRead { + toRead = rem + } + n, rerr := shard.ReadAt(buf[:toRead], offset) + if rerr != nil || int64(n) != toRead { + errs = append(errs, fmt.Errorf("ec volume %d shard %d: read block %d at %d: %v (got %d/%d)", + ecv.VolumeId, shard.ShardId, i, offset, rerr, n, toRead)) + shardBad = true + break + } + if uint32(needle.NewCRC(buf[:n])) != want[i] { + errs = append(errs, fmt.Errorf("ec volume %d shard %d: checksum mismatch at block %d (offset %d)", + ecv.VolumeId, shard.ShardId, i, offset)) + shardBad = true + } + offset += int64(n) + blocksScanned++ + } + if shardBad { + broken = append(broken, shard) + } + } + + // Wholesale-mismatch guard: localized bitrot touches a few shards; a + // stale/wrong sidecar mismatches more than parity_shards. Classify the + // latter as sidecar integrity, never as mass shard corruption. + if ecv.ECContext != nil && len(broken) > ecv.ECContext.ParityShards { + return blocksScanned, nil, []error{fmt.Errorf("ec volume %d: %d/%d local shards mismatch (> parity %d): suspect stale sidecar, not flagging shard corruption", + ecv.VolumeId, len(broken), len(ecv.Shards), ecv.ECContext.ParityShards)} + } + + // Reed-Solomon arbitration: a sidecar mismatch could mean the shard rotted + // OR the sidecar block is stale. When enough OTHER shards are local to + // reconstruct, reconstruct each flagged shard from the verified-clean shards + // and compare to its on-disk bytes. Only shards RS *also* disagrees with are + // truly corrupt; the rest are stale-sidecar false positives we must not + // quarantine. This is the arbiter that makes auto-repair safe. + if ecv.ECContext != nil && len(broken) > 0 { + brokenSet := make(map[ShardId]bool, len(broken)) + for _, s := range broken { + brokenSet[s.ShardId] = true + } + cleanCount := 0 + for _, s := range ecv.Shards { + if !brokenSet[s.ShardId] { + cleanCount++ + } + } + if cleanCount >= ecv.ECContext.DataShards { + confirmed := make([]*EcVolumeShard, 0, len(broken)) + for _, s := range broken { + corrupt, aerr := ecv.rsConfirmsShardCorrupt(s.ShardId, brokenSet, blockSize) + if aerr != nil { + // Could not arbitrate this shard; stay conservative and keep it. + errs = append(errs, fmt.Errorf("ec volume %d shard %d: RS arbitration failed: %v", ecv.VolumeId, s.ShardId, aerr)) + confirmed = append(confirmed, s) + continue + } + if corrupt { + confirmed = append(confirmed, s) + } else { + errs = append(errs, fmt.Errorf("ec volume %d shard %d: sidecar mismatch but Reed-Solomon confirms the bytes are correct: stale sidecar, not flagging shard", + ecv.VolumeId, s.ShardId)) + } + } + broken = confirmed + } + } + + for _, s := range broken { + brokenShards = append(brokenShards, s.ToEcShardInfo()) + } + slices.SortFunc(brokenShards, func(a, b *volume_server_pb.EcShardInfo) int { + return int(a.ShardId) - int(b.ShardId) + }) + return blocksScanned, brokenShards, errs +} + +// rsConfirmsShardCorrupt reconstructs targetId from the verified-clean local +// shards (all local shards except those in brokenSet) and compares the result +// to targetId's on-disk bytes block-by-block. It returns true only if +// Reed-Solomon disagrees with the disk (the shard is genuinely corrupt); a full +// match means the shard is fine and its sidecar block is stale. The caller +// guarantees at least DataShards clean shards are local before invoking this. +func (ecv *EcVolume) rsConfirmsShardCorrupt(targetId ShardId, brokenSet map[ShardId]bool, blockSize int64) (bool, error) { + enc, err := ecv.ECContext.CreateEncoder() + if err != nil { + return false, err + } + total := ecv.ECContext.Total() + local := make(map[ShardId]*EcVolumeShard, len(ecv.Shards)) + for _, s := range ecv.Shards { + local[s.ShardId] = s + } + target := local[targetId] + if target == nil { + return false, fmt.Errorf("target shard not local") + } + size := target.Size() + + buffers := make([][]byte, total) + diskBuf := make([]byte, blockSize) + var offset int64 + for offset < size { + n := blockSize + if rem := size - offset; rem < n { + n = rem + } + // Fill clean local shards; nil for the target and any broken/absent shard + // so Reconstruct regenerates the target from the trusted inputs. + for id := 0; id < total; id++ { + sid := ShardId(id) + if sid == targetId || brokenSet[sid] { + buffers[id] = nil + continue + } + s := local[sid] + if s == nil { + buffers[id] = nil + continue + } + buf := make([]byte, n) + got, rerr := s.ReadAt(buf, offset) + if rerr != nil || int64(got) != n { + return false, fmt.Errorf("read clean shard %d at %d: %v (got %d/%d)", sid, offset, rerr, got, n) + } + buffers[id] = buf + } + if err := enc.Reconstruct(buffers); err != nil { + return false, fmt.Errorf("reconstruct: %w", err) + } + got, rerr := target.ReadAt(diskBuf[:n], offset) + if rerr != nil || int64(got) != n { + return false, fmt.Errorf("read target shard %d at %d: %v", targetId, offset, rerr) + } + if !bytes.Equal(buffers[targetId][:n], diskBuf[:n]) { + return true, nil // Reed-Solomon disagrees with disk: genuinely corrupt. + } + offset += n + } + return false, nil // every block matches: shard is fine, sidecar block is stale. +} + // ScrubLocal checks the integrity of local shards for a EC volume. Notably, it cannot verify CRC on needles. // Returns a count of processed file entries, slice of found broken shards, and slice of found errors. func (ecv *EcVolume) ScrubLocal() (int64, []*volume_server_pb.EcShardInfo, []error) { diff --git a/weed/storage/erasure_coding/shard_distribution.go b/weed/storage/erasure_coding/shard_distribution.go index 591c60354..34ea64fbf 100644 --- a/weed/storage/erasure_coding/shard_distribution.go +++ b/weed/storage/erasure_coding/shard_distribution.go @@ -135,6 +135,9 @@ func DistributeEcShards(volumeID uint32, collection string, targets []*worker_pb if _, hasVif := shardFiles["vif"]; hasVif { assignedShards = append(assignedShards, "vif") } + if _, hasEcsum := shardFiles["ecsum"]; hasEcsum { + assignedShards = append(assignedShards, "ecsum") + } } if shardDisks[target.Node] == nil { @@ -322,6 +325,9 @@ func sendShardFileToDestination(volumeID uint32, collection string, dialOption g } else if shardType == "vif" { ext = ".vif" shardId = 0 + } else if shardType == "ecsum" { + ext = BitrotSidecarExt + shardId = 0 } else if strings.HasPrefix(shardType, "ec") && len(shardType) == 4 { ext = "." + shardType fmt.Sscanf(shardType[2:], "%d", &shardId) diff --git a/weed/storage/remote_tier_integration_test.go b/weed/storage/remote_tier_integration_test.go index 6326fc66e..11cbe2f91 100644 --- a/weed/storage/remote_tier_integration_test.go +++ b/weed/storage/remote_tier_integration_test.go @@ -299,7 +299,7 @@ func TestRemoteTier_ECEncode_RequiresLocalDat(t *testing.T) { tierUpVolume(t, dir, vid, b) baseFileName := filepath.Join(dir, fmt.Sprintf("%d", uint32(vid))) - err := erasure_coding.WriteEcFiles(baseFileName) + _, err := erasure_coding.WriteEcFiles(baseFileName, erasure_coding.BackgroundECContext()) require.Error(t, err, "EC encoder must not run with .dat missing — caller is expected to download first") require.Contains(t, err.Error(), ".dat") } @@ -321,7 +321,8 @@ func TestRemoteTier_ECEncodeDecode_AfterDownload(t *testing.T) { require.NoError(t, err) require.NoError(t, erasure_coding.WriteSortedFileFromIdx(baseFileName, ".ecx")) - require.NoError(t, erasure_coding.WriteEcFiles(baseFileName)) + _, ecErr := erasure_coding.WriteEcFiles(baseFileName, erasure_coding.BackgroundECContext()) + require.NoError(t, ecErr) for i := 0; i < erasure_coding.TotalShardsCount; i++ { shardPath := fmt.Sprintf("%s.ec%02d", baseFileName, i) @@ -335,7 +336,7 @@ func TestRemoteTier_ECEncodeDecode_AfterDownload(t *testing.T) { shardPath := fmt.Sprintf("%s.ec%02d", baseFileName, i) require.NoError(t, os.Remove(shardPath)) } - rebuilt, err := erasure_coding.RebuildEcFiles(baseFileName) + rebuilt, err := erasure_coding.RebuildEcFiles(baseFileName, erasure_coding.BackgroundECContext(), false) require.NoError(t, err) require.NotEmpty(t, rebuilt, "rebuild should report which parity shards were regenerated") for i := erasure_coding.DataShardsCount; i < erasure_coding.TotalShardsCount; i++ { diff --git a/weed/worker/tasks/erasure_coding/ec_task.go b/weed/worker/tasks/erasure_coding/ec_task.go index bcc7bb880..935883119 100644 --- a/weed/worker/tasks/erasure_coding/ec_task.go +++ b/weed/worker/tasks/erasure_coding/ec_task.go @@ -558,9 +558,18 @@ func (t *ErasureCodingTask) generateEcShardsLocally(localFiles map[string]string } // Generate EC shard files (.ec00 ~ .ec13) - if err := erasure_coding.WriteEcFiles(baseName); err != nil { + ecBitrot, err := erasure_coding.WriteEcFiles(baseName, erasure_coding.BackgroundECContext()) + if err != nil { return nil, fmt.Errorf("failed to generate EC shard files: %v", err) } + // Persist the bitrot checksum sidecar (generation 0) alongside the shards so + // it travels with them during distribution. Best-effort: a failed sidecar + // write leaves the generation unprotected rather than failing the encode. + if erasure_coding.BitrotProtectionEnabled && ecBitrot != nil { + if serr := erasure_coding.SaveBitrotSidecar(erasure_coding.BitrotSidecarPath(baseName, 0), ecBitrot); serr != nil { + glog.Warningf("failed to write EC bitrot sidecar for %s: %v", baseName, serr) + } + } // Collect generated shard file paths and log details var generatedShards []string @@ -625,6 +634,20 @@ func (t *ErasureCodingTask) generateEcShardsLocally(localFiles map[string]string } } + // Add the generation-0 bitrot checksum sidecar so it is distributed with + // the shards (DistributeEcShards only ships files present in shardFiles). + // Best-effort like the sidecar write above: if it is absent the holders + // are simply unprotected rather than failing the encode. + ecsumFile := erasure_coding.BitrotSidecarPath(baseName, 0) + if info, err := os.Stat(ecsumFile); err == nil { + shardFiles["ecsum"] = ecsumFile + t.GetLogger().WithFields(map[string]interface{}{ + "file_type": "ecsum", + "file_path": ecsumFile, + "size_bytes": info.Size(), + }).Info("EC bitrot checksum sidecar generated") + } + // Log summary of generation t.GetLogger().WithFields(map[string]interface{}{ "total_files": len(shardFiles),