You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							553 lines
						
					
					
						
							18 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							553 lines
						
					
					
						
							18 KiB
						
					
					
				
								package weed_server
							 | 
						|
								
							 | 
						|
								import (
							 | 
						|
									"context"
							 | 
						|
									"fmt"
							 | 
						|
									"io"
							 | 
						|
									"math"
							 | 
						|
									"os"
							 | 
						|
									"path"
							 | 
						|
									"strings"
							 | 
						|
									"time"
							 | 
						|
								
							 | 
						|
									"github.com/seaweedfs/seaweedfs/weed/glog"
							 | 
						|
									"github.com/seaweedfs/seaweedfs/weed/operation"
							 | 
						|
									"github.com/seaweedfs/seaweedfs/weed/pb"
							 | 
						|
									"github.com/seaweedfs/seaweedfs/weed/pb/volume_server_pb"
							 | 
						|
									"github.com/seaweedfs/seaweedfs/weed/storage"
							 | 
						|
									"github.com/seaweedfs/seaweedfs/weed/storage/erasure_coding"
							 | 
						|
									"github.com/seaweedfs/seaweedfs/weed/storage/needle"
							 | 
						|
									"github.com/seaweedfs/seaweedfs/weed/storage/types"
							 | 
						|
									"github.com/seaweedfs/seaweedfs/weed/storage/volume_info"
							 | 
						|
									"github.com/seaweedfs/seaweedfs/weed/util"
							 | 
						|
								)
							 | 
						|
								
							 | 
						|
								/*
							 | 
						|
								
							 | 
						|
								Steps to apply erasure coding to .dat .idx files
							 | 
						|
								0. ensure the volume is readonly
							 | 
						|
								1. client call VolumeEcShardsGenerate to generate the .ecx and .ec00 ~ .ec13 files
							 | 
						|
								2. client ask master for possible servers to hold the ec files
							 | 
						|
								3. client call VolumeEcShardsCopy on above target servers to copy ec files from the source server
							 | 
						|
								4. target servers report the new ec files to the master
							 | 
						|
								5.   master stores vid -> [14]*DataNode
							 | 
						|
								6. client checks master. If all 14 slices are ready, delete the original .idx, .idx files
							 | 
						|
								
							 | 
						|
								*/
							 | 
						|
								
							 | 
						|
								// VolumeEcShardsGenerate generates the .ecx and .ec00 ~ .ec13 files
							 | 
						|
								func (vs *VolumeServer) VolumeEcShardsGenerate(ctx context.Context, req *volume_server_pb.VolumeEcShardsGenerateRequest) (*volume_server_pb.VolumeEcShardsGenerateResponse, error) {
							 | 
						|
								
							 | 
						|
									glog.V(0).Infof("VolumeEcShardsGenerate: %v", req)
							 | 
						|
								
							 | 
						|
									v := vs.store.GetVolume(needle.VolumeId(req.VolumeId))
							 | 
						|
									if v == nil {
							 | 
						|
										return nil, fmt.Errorf("volume %d not found", req.VolumeId)
							 | 
						|
									}
							 | 
						|
									baseFileName := v.DataFileName()
							 | 
						|
								
							 | 
						|
									if v.Collection != req.Collection {
							 | 
						|
										return nil, fmt.Errorf("existing collection:%v unexpected input: %v", v.Collection, req.Collection)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Create EC context - prefer existing .vif config if present (for regeneration scenarios)
							 | 
						|
									ecCtx := erasure_coding.NewDefaultECContext(req.Collection, needle.VolumeId(req.VolumeId))
							 | 
						|
									if volumeInfo, _, found, _ := volume_info.MaybeLoadVolumeInfo(baseFileName + ".vif"); found && volumeInfo.EcShardConfig != nil {
							 | 
						|
										ds := int(volumeInfo.EcShardConfig.DataShards)
							 | 
						|
										ps := int(volumeInfo.EcShardConfig.ParityShards)
							 | 
						|
								
							 | 
						|
										// Validate and use existing EC config
							 | 
						|
										if ds > 0 && ps > 0 && ds+ps <= erasure_coding.MaxShardCount {
							 | 
						|
											ecCtx.DataShards = ds
							 | 
						|
											ecCtx.ParityShards = ps
							 | 
						|
											glog.V(0).Infof("Using existing EC config for volume %d: %s", req.VolumeId, ecCtx.String())
							 | 
						|
										} else {
							 | 
						|
											glog.Warningf("Invalid EC config in .vif for volume %d (data=%d, parity=%d), using defaults", req.VolumeId, ds, ps)
							 | 
						|
										}
							 | 
						|
									} else {
							 | 
						|
										glog.V(0).Infof("Using default EC config for volume %d: %s", req.VolumeId, ecCtx.String())
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									shouldCleanup := true
							 | 
						|
									defer func() {
							 | 
						|
										if !shouldCleanup {
							 | 
						|
											return
							 | 
						|
										}
							 | 
						|
										for i := 0; i < ecCtx.Total(); i++ {
							 | 
						|
											os.Remove(baseFileName + ecCtx.ToExt(i))
							 | 
						|
										}
							 | 
						|
										os.Remove(v.IndexFileName() + ".ecx")
							 | 
						|
									}()
							 | 
						|
								
							 | 
						|
									// 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)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// write .ecx file
							 | 
						|
									if err := erasure_coding.WriteSortedFileFromIdx(v.IndexFileName(), ".ecx"); err != nil {
							 | 
						|
										return nil, fmt.Errorf("WriteSortedFileFromIdx %s: %v", v.IndexFileName(), err)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// write .vif files
							 | 
						|
									var expireAtSec uint64
							 | 
						|
									if v.Ttl != nil {
							 | 
						|
										ttlSecond := v.Ttl.ToSeconds()
							 | 
						|
										if ttlSecond > 0 {
							 | 
						|
											expireAtSec = uint64(time.Now().Unix()) + ttlSecond //calculated expiration time
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
									volumeInfo := &volume_server_pb.VolumeInfo{Version: uint32(v.Version())}
							 | 
						|
									volumeInfo.ExpireAtSec = expireAtSec
							 | 
						|
								
							 | 
						|
									datSize, _, _ := v.FileStat()
							 | 
						|
									volumeInfo.DatFileSize = int64(datSize)
							 | 
						|
								
							 | 
						|
									// Validate EC configuration before saving to .vif
							 | 
						|
									if ecCtx.DataShards <= 0 || ecCtx.ParityShards <= 0 || ecCtx.Total() > erasure_coding.MaxShardCount {
							 | 
						|
										return nil, fmt.Errorf("invalid EC config before saving: data=%d, parity=%d, total=%d (max=%d)",
							 | 
						|
											ecCtx.DataShards, ecCtx.ParityShards, ecCtx.Total(), erasure_coding.MaxShardCount)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Save EC configuration to VolumeInfo
							 | 
						|
									volumeInfo.EcShardConfig = &volume_server_pb.EcShardConfig{
							 | 
						|
										DataShards:   uint32(ecCtx.DataShards),
							 | 
						|
										ParityShards: uint32(ecCtx.ParityShards),
							 | 
						|
									}
							 | 
						|
									glog.V(1).Infof("Saving EC config to .vif for volume %d: %d+%d (total: %d)",
							 | 
						|
										req.VolumeId, ecCtx.DataShards, ecCtx.ParityShards, ecCtx.Total())
							 | 
						|
								
							 | 
						|
									if err := volume_info.SaveVolumeInfo(baseFileName+".vif", volumeInfo); err != nil {
							 | 
						|
										return nil, fmt.Errorf("SaveVolumeInfo %s: %v", baseFileName, err)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									shouldCleanup = false
							 | 
						|
								
							 | 
						|
									return &volume_server_pb.VolumeEcShardsGenerateResponse{}, nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// VolumeEcShardsRebuild generates the any of the missing .ec00 ~ .ec13 files
							 | 
						|
								func (vs *VolumeServer) VolumeEcShardsRebuild(ctx context.Context, req *volume_server_pb.VolumeEcShardsRebuildRequest) (*volume_server_pb.VolumeEcShardsRebuildResponse, error) {
							 | 
						|
								
							 | 
						|
									glog.V(0).Infof("VolumeEcShardsRebuild: %v", req)
							 | 
						|
								
							 | 
						|
									baseFileName := erasure_coding.EcShardBaseFileName(req.Collection, int(req.VolumeId))
							 | 
						|
								
							 | 
						|
									var rebuiltShardIds []uint32
							 | 
						|
								
							 | 
						|
									for _, location := range vs.store.Locations {
							 | 
						|
										_, _, existingShardCount, err := checkEcVolumeStatus(baseFileName, location)
							 | 
						|
										if err != nil {
							 | 
						|
											return nil, err
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										if existingShardCount == 0 {
							 | 
						|
											continue
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										if util.FileExists(path.Join(location.IdxDirectory, baseFileName+".ecx")) {
							 | 
						|
											// write .ec00 ~ .ec13 files
							 | 
						|
											dataBaseFileName := path.Join(location.Directory, baseFileName)
							 | 
						|
											if generatedShardIds, err := erasure_coding.RebuildEcFiles(dataBaseFileName); err != nil {
							 | 
						|
												return nil, fmt.Errorf("RebuildEcFiles %s: %v", dataBaseFileName, err)
							 | 
						|
											} else {
							 | 
						|
												rebuiltShardIds = generatedShardIds
							 | 
						|
											}
							 | 
						|
								
							 | 
						|
											indexBaseFileName := path.Join(location.IdxDirectory, baseFileName)
							 | 
						|
											if err := erasure_coding.RebuildEcxFile(indexBaseFileName); err != nil {
							 | 
						|
												return nil, fmt.Errorf("RebuildEcxFile %s: %v", dataBaseFileName, err)
							 | 
						|
											}
							 | 
						|
								
							 | 
						|
											break
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return &volume_server_pb.VolumeEcShardsRebuildResponse{
							 | 
						|
										RebuiltShardIds: rebuiltShardIds,
							 | 
						|
									}, nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// VolumeEcShardsCopy copy the .ecx and some ec data slices
							 | 
						|
								func (vs *VolumeServer) VolumeEcShardsCopy(ctx context.Context, req *volume_server_pb.VolumeEcShardsCopyRequest) (*volume_server_pb.VolumeEcShardsCopyResponse, error) {
							 | 
						|
								
							 | 
						|
									glog.V(0).Infof("VolumeEcShardsCopy: %v", req)
							 | 
						|
								
							 | 
						|
									var location *storage.DiskLocation
							 | 
						|
								
							 | 
						|
									// Use disk_id if provided (disk-aware storage)
							 | 
						|
									if req.DiskId > 0 || (req.DiskId == 0 && len(vs.store.Locations) > 0) {
							 | 
						|
										// Validate disk ID is within bounds
							 | 
						|
										if int(req.DiskId) >= len(vs.store.Locations) {
							 | 
						|
											return nil, fmt.Errorf("invalid disk_id %d: only have %d disks", req.DiskId, len(vs.store.Locations))
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										// Use the specific disk location
							 | 
						|
										location = vs.store.Locations[req.DiskId]
							 | 
						|
										glog.V(1).Infof("Using disk %d for EC shard copy: %s", req.DiskId, location.Directory)
							 | 
						|
									} else {
							 | 
						|
										// Fallback to old behavior for backward compatibility
							 | 
						|
										if req.CopyEcxFile {
							 | 
						|
											location = vs.store.FindFreeLocation(func(location *storage.DiskLocation) bool {
							 | 
						|
												return location.DiskType == types.HardDriveType
							 | 
						|
											})
							 | 
						|
										} else {
							 | 
						|
											location = vs.store.FindFreeLocation(func(location *storage.DiskLocation) bool {
							 | 
						|
												return true
							 | 
						|
											})
							 | 
						|
										}
							 | 
						|
										if location == nil {
							 | 
						|
											return nil, fmt.Errorf("no space left")
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									dataBaseFileName := storage.VolumeFileName(location.Directory, req.Collection, int(req.VolumeId))
							 | 
						|
									indexBaseFileName := storage.VolumeFileName(location.IdxDirectory, req.Collection, int(req.VolumeId))
							 | 
						|
								
							 | 
						|
									err := operation.WithVolumeServerClient(true, pb.ServerAddress(req.SourceDataNode), vs.grpcDialOption, func(client volume_server_pb.VolumeServerClient) error {
							 | 
						|
								
							 | 
						|
										// copy ec data slices
							 | 
						|
										for _, shardId := range req.ShardIds {
							 | 
						|
											if _, err := vs.doCopyFile(client, true, req.Collection, req.VolumeId, math.MaxUint32, math.MaxInt64, dataBaseFileName, erasure_coding.ToExt(int(shardId)), false, false, nil); err != nil {
							 | 
						|
												return err
							 | 
						|
											}
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										if req.CopyEcxFile {
							 | 
						|
								
							 | 
						|
											// copy ecx file
							 | 
						|
											if _, err := vs.doCopyFile(client, true, req.Collection, req.VolumeId, math.MaxUint32, math.MaxInt64, indexBaseFileName, ".ecx", false, false, nil); err != nil {
							 | 
						|
												return err
							 | 
						|
											}
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										if req.CopyEcjFile {
							 | 
						|
											// copy ecj file
							 | 
						|
											if _, err := vs.doCopyFile(client, true, req.Collection, req.VolumeId, math.MaxUint32, math.MaxInt64, indexBaseFileName, ".ecj", true, true, nil); err != nil {
							 | 
						|
												return err
							 | 
						|
											}
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										if req.CopyVifFile {
							 | 
						|
											// copy vif file
							 | 
						|
											if _, err := vs.doCopyFile(client, true, req.Collection, req.VolumeId, math.MaxUint32, math.MaxInt64, dataBaseFileName, ".vif", false, true, nil); err != nil {
							 | 
						|
												return err
							 | 
						|
											}
							 | 
						|
										}
							 | 
						|
										return nil
							 | 
						|
									})
							 | 
						|
									if err != nil {
							 | 
						|
										return nil, fmt.Errorf("VolumeEcShardsCopy volume %d: %v", req.VolumeId, err)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return &volume_server_pb.VolumeEcShardsCopyResponse{}, nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// VolumeEcShardsDelete local delete the .ecx and some ec data slices if not needed
							 | 
						|
								// the shard should not be mounted before calling this.
							 | 
						|
								func (vs *VolumeServer) VolumeEcShardsDelete(ctx context.Context, req *volume_server_pb.VolumeEcShardsDeleteRequest) (*volume_server_pb.VolumeEcShardsDeleteResponse, error) {
							 | 
						|
								
							 | 
						|
									bName := erasure_coding.EcShardBaseFileName(req.Collection, int(req.VolumeId))
							 | 
						|
								
							 | 
						|
									glog.V(0).Infof("ec volume %s shard delete %v", bName, req.ShardIds)
							 | 
						|
								
							 | 
						|
									for _, location := range vs.store.Locations {
							 | 
						|
										if err := deleteEcShardIdsForEachLocation(bName, location, req.ShardIds); err != nil {
							 | 
						|
											glog.Errorf("deleteEcShards from %s %s.%v: %v", location.Directory, bName, req.ShardIds, err)
							 | 
						|
											return nil, err
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return &volume_server_pb.VolumeEcShardsDeleteResponse{}, nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func deleteEcShardIdsForEachLocation(bName string, location *storage.DiskLocation, shardIds []uint32) error {
							 | 
						|
								
							 | 
						|
									found := false
							 | 
						|
								
							 | 
						|
									indexBaseFilename := path.Join(location.IdxDirectory, bName)
							 | 
						|
									dataBaseFilename := path.Join(location.Directory, bName)
							 | 
						|
								
							 | 
						|
									if util.FileExists(path.Join(location.IdxDirectory, bName+".ecx")) {
							 | 
						|
										for _, shardId := range shardIds {
							 | 
						|
											shardFileName := dataBaseFilename + erasure_coding.ToExt(int(shardId))
							 | 
						|
											if util.FileExists(shardFileName) {
							 | 
						|
												found = true
							 | 
						|
												os.Remove(shardFileName)
							 | 
						|
											}
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									if !found {
							 | 
						|
										return nil
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									hasEcxFile, hasIdxFile, existingShardCount, err := checkEcVolumeStatus(bName, location)
							 | 
						|
									if err != nil {
							 | 
						|
										return err
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									if hasEcxFile && existingShardCount == 0 {
							 | 
						|
										if err := os.Remove(indexBaseFilename + ".ecx"); err != nil {
							 | 
						|
											return err
							 | 
						|
										}
							 | 
						|
										os.Remove(indexBaseFilename + ".ecj")
							 | 
						|
								
							 | 
						|
										if !hasIdxFile {
							 | 
						|
											// .vif is used for ec volumes and normal volumes
							 | 
						|
											os.Remove(dataBaseFilename + ".vif")
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								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)
							 | 
						|
									if err != nil {
							 | 
						|
										return false, false, 0, err
							 | 
						|
									}
							 | 
						|
									if location.IdxDirectory != location.Directory {
							 | 
						|
										idxFileInfos, err := os.ReadDir(location.IdxDirectory)
							 | 
						|
										if err != nil {
							 | 
						|
											return false, false, 0, err
							 | 
						|
										}
							 | 
						|
										fileInfos = append(fileInfos, idxFileInfos...)
							 | 
						|
									}
							 | 
						|
									for _, fileInfo := range fileInfos {
							 | 
						|
										if fileInfo.Name() == bName+".ecx" || fileInfo.Name() == bName+".ecj" {
							 | 
						|
											hasEcxFile = true
							 | 
						|
											continue
							 | 
						|
										}
							 | 
						|
										if fileInfo.Name() == bName+".idx" {
							 | 
						|
											hasIdxFile = true
							 | 
						|
											continue
							 | 
						|
										}
							 | 
						|
										if strings.HasPrefix(fileInfo.Name(), bName+".ec") {
							 | 
						|
											existingShardCount++
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
									return hasEcxFile, hasIdxFile, existingShardCount, nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (vs *VolumeServer) VolumeEcShardsMount(ctx context.Context, req *volume_server_pb.VolumeEcShardsMountRequest) (*volume_server_pb.VolumeEcShardsMountResponse, error) {
							 | 
						|
								
							 | 
						|
									glog.V(0).Infof("VolumeEcShardsMount: %v", req)
							 | 
						|
								
							 | 
						|
									for _, shardId := range req.ShardIds {
							 | 
						|
										err := vs.store.MountEcShards(req.Collection, needle.VolumeId(req.VolumeId), erasure_coding.ShardId(shardId))
							 | 
						|
								
							 | 
						|
										if err != nil {
							 | 
						|
											glog.Errorf("ec shard mount %v: %v", req, err)
							 | 
						|
										} else {
							 | 
						|
											glog.V(2).Infof("ec shard mount %v", req)
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										if err != nil {
							 | 
						|
											return nil, fmt.Errorf("mount %d.%d: %v", req.VolumeId, shardId, err)
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return &volume_server_pb.VolumeEcShardsMountResponse{}, nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (vs *VolumeServer) VolumeEcShardsUnmount(ctx context.Context, req *volume_server_pb.VolumeEcShardsUnmountRequest) (*volume_server_pb.VolumeEcShardsUnmountResponse, error) {
							 | 
						|
								
							 | 
						|
									glog.V(0).Infof("VolumeEcShardsUnmount: %v", req)
							 | 
						|
								
							 | 
						|
									for _, shardId := range req.ShardIds {
							 | 
						|
										err := vs.store.UnmountEcShards(needle.VolumeId(req.VolumeId), erasure_coding.ShardId(shardId))
							 | 
						|
								
							 | 
						|
										if err != nil {
							 | 
						|
											glog.Errorf("ec shard unmount %v: %v", req, err)
							 | 
						|
										} else {
							 | 
						|
											glog.V(2).Infof("ec shard unmount %v", req)
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										if err != nil {
							 | 
						|
											return nil, fmt.Errorf("unmount %d.%d: %v", req.VolumeId, shardId, err)
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return &volume_server_pb.VolumeEcShardsUnmountResponse{}, nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (vs *VolumeServer) VolumeEcShardRead(req *volume_server_pb.VolumeEcShardReadRequest, stream volume_server_pb.VolumeServer_VolumeEcShardReadServer) error {
							 | 
						|
								
							 | 
						|
									ecVolume, found := vs.store.FindEcVolume(needle.VolumeId(req.VolumeId))
							 | 
						|
									if !found {
							 | 
						|
										return fmt.Errorf("VolumeEcShardRead not found ec volume id %d", req.VolumeId)
							 | 
						|
									}
							 | 
						|
									ecShard, found := ecVolume.FindEcVolumeShard(erasure_coding.ShardId(req.ShardId))
							 | 
						|
									if !found {
							 | 
						|
										return fmt.Errorf("not found ec shard %d.%d", req.VolumeId, req.ShardId)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									if req.FileKey != 0 {
							 | 
						|
										_, size, _ := ecVolume.FindNeedleFromEcx(types.Uint64ToNeedleId(req.FileKey))
							 | 
						|
										if size.IsDeleted() {
							 | 
						|
											return stream.Send(&volume_server_pb.VolumeEcShardReadResponse{
							 | 
						|
												IsDeleted: true,
							 | 
						|
											})
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									bufSize := req.Size
							 | 
						|
									if bufSize > BufferSizeLimit {
							 | 
						|
										bufSize = BufferSizeLimit
							 | 
						|
									}
							 | 
						|
									buffer := make([]byte, bufSize)
							 | 
						|
								
							 | 
						|
									startOffset, bytesToRead := req.Offset, req.Size
							 | 
						|
								
							 | 
						|
									for bytesToRead > 0 {
							 | 
						|
										// min of bytesToRead and bufSize
							 | 
						|
										bufferSize := bufSize
							 | 
						|
										if bufferSize > bytesToRead {
							 | 
						|
											bufferSize = bytesToRead
							 | 
						|
										}
							 | 
						|
										bytesread, err := ecShard.ReadAt(buffer[0:bufferSize], startOffset)
							 | 
						|
								
							 | 
						|
										// println("read", ecShard.FileName(), "startOffset", startOffset, bytesread, "bytes, with target", bufferSize)
							 | 
						|
										if bytesread > 0 {
							 | 
						|
								
							 | 
						|
											if int64(bytesread) > bytesToRead {
							 | 
						|
												bytesread = int(bytesToRead)
							 | 
						|
											}
							 | 
						|
											err = stream.Send(&volume_server_pb.VolumeEcShardReadResponse{
							 | 
						|
												Data: buffer[:bytesread],
							 | 
						|
											})
							 | 
						|
											if err != nil {
							 | 
						|
												// println("sending", bytesread, "bytes err", err.Error())
							 | 
						|
												return err
							 | 
						|
											}
							 | 
						|
								
							 | 
						|
											startOffset += int64(bytesread)
							 | 
						|
											bytesToRead -= int64(bytesread)
							 | 
						|
								
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										if err != nil {
							 | 
						|
											if err != io.EOF {
							 | 
						|
												return err
							 | 
						|
											}
							 | 
						|
											return nil
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return nil
							 | 
						|
								
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (vs *VolumeServer) VolumeEcBlobDelete(ctx context.Context, req *volume_server_pb.VolumeEcBlobDeleteRequest) (*volume_server_pb.VolumeEcBlobDeleteResponse, error) {
							 | 
						|
								
							 | 
						|
									glog.V(0).Infof("VolumeEcBlobDelete: %v", req)
							 | 
						|
								
							 | 
						|
									resp := &volume_server_pb.VolumeEcBlobDeleteResponse{}
							 | 
						|
								
							 | 
						|
									for _, location := range vs.store.Locations {
							 | 
						|
										if localEcVolume, found := location.FindEcVolume(needle.VolumeId(req.VolumeId)); found {
							 | 
						|
								
							 | 
						|
											_, size, _, err := localEcVolume.LocateEcShardNeedle(types.NeedleId(req.FileKey), needle.Version(req.Version))
							 | 
						|
											if err != nil {
							 | 
						|
												return nil, fmt.Errorf("locate in local ec volume: %w", err)
							 | 
						|
											}
							 | 
						|
											if size.IsDeleted() {
							 | 
						|
												return resp, nil
							 | 
						|
											}
							 | 
						|
								
							 | 
						|
											err = localEcVolume.DeleteNeedleFromEcx(types.NeedleId(req.FileKey))
							 | 
						|
											if err != nil {
							 | 
						|
												return nil, err
							 | 
						|
											}
							 | 
						|
								
							 | 
						|
											break
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return resp, nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// VolumeEcShardsToVolume generates the .idx, .dat files from .ecx, .ecj and .ec01 ~ .ec14 files
							 | 
						|
								func (vs *VolumeServer) VolumeEcShardsToVolume(ctx context.Context, req *volume_server_pb.VolumeEcShardsToVolumeRequest) (*volume_server_pb.VolumeEcShardsToVolumeResponse, error) {
							 | 
						|
								
							 | 
						|
									glog.V(0).Infof("VolumeEcShardsToVolume: %v", req)
							 | 
						|
								
							 | 
						|
									// Collect all EC shards (NewEcVolume will load EC config from .vif into v.ECContext)
							 | 
						|
									// Use MaxShardCount (32) to support custom EC ratios up to 32 total shards
							 | 
						|
									tempShards := make([]string, erasure_coding.MaxShardCount)
							 | 
						|
									v, found := vs.store.CollectEcShards(needle.VolumeId(req.VolumeId), tempShards)
							 | 
						|
									if !found {
							 | 
						|
										return nil, fmt.Errorf("ec volume %d not found", req.VolumeId)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									if v.Collection != req.Collection {
							 | 
						|
										return nil, fmt.Errorf("existing collection:%v unexpected input: %v", v.Collection, req.Collection)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Use EC context (already loaded from .vif) to determine data shard count
							 | 
						|
									dataShards := v.ECContext.DataShards
							 | 
						|
								
							 | 
						|
									// Defensive validation to prevent panics from corrupted ECContext
							 | 
						|
									if dataShards <= 0 || dataShards > erasure_coding.MaxShardCount {
							 | 
						|
										return nil, fmt.Errorf("invalid data shard count %d for volume %d (must be 1..%d)", dataShards, req.VolumeId, erasure_coding.MaxShardCount)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									shardFileNames := tempShards[:dataShards]
							 | 
						|
									glog.V(1).Infof("Using EC config from volume %d: %d data shards", req.VolumeId, dataShards)
							 | 
						|
								
							 | 
						|
									// Verify all data shards are present
							 | 
						|
									for shardId := 0; shardId < dataShards; shardId++ {
							 | 
						|
										if shardFileNames[shardId] == "" {
							 | 
						|
											return nil, fmt.Errorf("ec volume %d missing shard %d", req.VolumeId, shardId)
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									dataBaseFileName, indexBaseFileName := v.DataBaseFileName(), v.IndexBaseFileName()
							 | 
						|
									// calculate .dat file size
							 | 
						|
									datFileSize, err := erasure_coding.FindDatFileSize(dataBaseFileName, indexBaseFileName)
							 | 
						|
									if err != nil {
							 | 
						|
										return nil, fmt.Errorf("FindDatFileSize %s: %v", dataBaseFileName, err)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// write .dat file from .ec00 ~ .ec09 files
							 | 
						|
									if err := erasure_coding.WriteDatFile(dataBaseFileName, datFileSize, shardFileNames); err != nil {
							 | 
						|
										return nil, fmt.Errorf("WriteDatFile %s: %v", dataBaseFileName, err)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// write .idx file from .ecx and .ecj files
							 | 
						|
									if err := erasure_coding.WriteIdxFileFromEcIndex(indexBaseFileName); err != nil {
							 | 
						|
										return nil, fmt.Errorf("WriteIdxFileFromEcIndex %s: %v", v.IndexBaseFileName(), err)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return &volume_server_pb.VolumeEcShardsToVolumeResponse{}, nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								func (vs *VolumeServer) VolumeEcShardsInfo(ctx context.Context, req *volume_server_pb.VolumeEcShardsInfoRequest) (*volume_server_pb.VolumeEcShardsInfoResponse, error) {
							 | 
						|
									glog.V(0).Infof("VolumeEcShardsInfo: volume %d", req.VolumeId)
							 | 
						|
								
							 | 
						|
									var ecShardInfos []*volume_server_pb.EcShardInfo
							 | 
						|
								
							 | 
						|
									// Find the EC volume
							 | 
						|
									for _, location := range vs.store.Locations {
							 | 
						|
										if v, found := location.FindEcVolume(needle.VolumeId(req.VolumeId)); found {
							 | 
						|
											// Get shard details from the EC volume
							 | 
						|
											shardDetails := v.ShardDetails()
							 | 
						|
											for _, shardDetail := range shardDetails {
							 | 
						|
												ecShardInfo := &volume_server_pb.EcShardInfo{
							 | 
						|
													ShardId:    uint32(shardDetail.ShardId),
							 | 
						|
													Size:       int64(shardDetail.Size),
							 | 
						|
													Collection: v.Collection,
							 | 
						|
												}
							 | 
						|
												ecShardInfos = append(ecShardInfos, ecShardInfo)
							 | 
						|
											}
							 | 
						|
											break
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return &volume_server_pb.VolumeEcShardsInfoResponse{
							 | 
						|
										EcShardInfos: ecShardInfos,
							 | 
						|
									}, nil
							 | 
						|
								}
							 |