mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-09-29 03:05:59 +00:00
[Volume] Validate record counts after volume copy (#11238)
* validate Volume Copy record counts * Delete s3api_object_versioning_bench_test.go * reply ai comments
This commit is contained in:
@@ -50,11 +50,20 @@ func (vs *VolumeServer) VolumeCopy(req *volume_server_pb.VolumeCopyRequest, stre
|
||||
// send .dat file
|
||||
// confirm size and timestamp
|
||||
var volFileInfoResp *volume_server_pb.ReadVolumeFileStatusResponse
|
||||
var sourceVolumeStatus *volume_server_pb.VolumeStatusResponse
|
||||
var sourceVolumeStatusAfterCopy *volume_server_pb.VolumeStatusResponse
|
||||
var dataBaseFileName, indexBaseFileName, idxFileName, datFileName string
|
||||
var hasRemoteDatFile bool
|
||||
err := operation.WithVolumeServerClient(true, pb.ServerAddress(req.SourceDataNode), vs.grpcDialOption, func(client volume_server_pb.VolumeServerClient) error {
|
||||
var err error
|
||||
volFileInfoResp, err = client.ReadVolumeFileStatus(context.Background(),
|
||||
sourceVolumeStatus, err = client.VolumeStatus(stream.Context(), &volume_server_pb.VolumeStatusRequest{
|
||||
VolumeId: req.VolumeId,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("read volume status failed, %w", err)
|
||||
}
|
||||
|
||||
volFileInfoResp, err = client.ReadVolumeFileStatus(stream.Context(),
|
||||
&volume_server_pb.ReadVolumeFileStatusRequest{
|
||||
VolumeId: req.VolumeId,
|
||||
})
|
||||
@@ -189,6 +198,15 @@ func (vs *VolumeServer) VolumeCopy(req *volume_server_pb.VolumeCopyRequest, stre
|
||||
return fmt.Errorf("remove .note for volume %d: %w", req.VolumeId, noteErr)
|
||||
}
|
||||
|
||||
var statusErr error
|
||||
sourceVolumeStatusAfterCopy, statusErr = client.VolumeStatus(stream.Context(), &volume_server_pb.VolumeStatusRequest{
|
||||
VolumeId: req.VolumeId,
|
||||
})
|
||||
if statusErr != nil {
|
||||
glog.Warningf("failed to read source volume %d status after copy; skip record count validation: %v", req.VolumeId, statusErr)
|
||||
sourceVolumeStatusAfterCopy = nil
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
@@ -223,10 +241,24 @@ func (vs *VolumeServer) VolumeCopy(req *volume_server_pb.VolumeCopyRequest, stre
|
||||
}
|
||||
}
|
||||
|
||||
// mount the volume
|
||||
err = vs.store.MountVolume(needle.VolumeId(req.VolumeId))
|
||||
shouldValidateCopyCounts := copyCountsStable(sourceVolumeStatus, sourceVolumeStatusAfterCopy)
|
||||
if sourceVolumeStatusAfterCopy == nil {
|
||||
glog.V(1).Infof("source volume %d status was unavailable after copy; skip record count validation", req.VolumeId)
|
||||
} else if !shouldValidateCopyCounts {
|
||||
glog.V(1).Infof("source volume %d changed during copy; skip record count validation", req.VolumeId)
|
||||
}
|
||||
|
||||
// Load and validate the volume before announcing it to the master. A failed
|
||||
// validation is unloaded by the store without ever making the replica
|
||||
// routable.
|
||||
err = vs.store.MountVolumeWithValidator(needle.VolumeId(req.VolumeId), func(targetVolume *storage.Volume) error {
|
||||
if !shouldValidateCopyCounts {
|
||||
return nil
|
||||
}
|
||||
return checkCopyCounts(sourceVolumeStatusAfterCopy, targetVolume.FileCount(), targetVolume.DeletedCount())
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to mount volume %d: %v", req.VolumeId, err)
|
||||
return fmt.Errorf("failed to mount or validate volume %d: %w", req.VolumeId, err)
|
||||
}
|
||||
|
||||
if err = stream.Send(&volume_server_pb.VolumeCopyResponse{
|
||||
@@ -266,11 +298,8 @@ func (vs *VolumeServer) doCopyFileWithThrottler(client volume_server_pb.VolumeSe
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
only check the differ of the file size
|
||||
todo: maybe should check the received count and deleted count of the volume
|
||||
*/
|
||||
// checkCopyFiles verifies the copied file sizes. Record counts are checked
|
||||
// after the target volume is mounted, when the target needle map is available.
|
||||
func checkCopyFiles(originFileInf *volume_server_pb.ReadVolumeFileStatusResponse, hasRemoteDatFile bool, idxFileName, datFileName string) error {
|
||||
stat, err := os.Stat(idxFileName)
|
||||
if err != nil {
|
||||
@@ -300,6 +329,22 @@ func checkCopyFiles(originFileInf *volume_server_pb.ReadVolumeFileStatusResponse
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkCopyCounts(origin *volume_server_pb.VolumeStatusResponse, targetFileCount, targetDeletedCount uint64) error {
|
||||
if origin.FileCount != targetFileCount {
|
||||
return fmt.Errorf("target file count [%d] is not same as origin file count [%d]", targetFileCount, origin.FileCount)
|
||||
}
|
||||
if origin.FileDeletedCount != targetDeletedCount {
|
||||
return fmt.Errorf("target deleted count [%d] is not same as origin deleted count [%d]", targetDeletedCount, origin.FileDeletedCount)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func copyCountsStable(before, after *volume_server_pb.VolumeStatusResponse) bool {
|
||||
return before != nil && after != nil &&
|
||||
before.FileCount == after.FileCount &&
|
||||
before.FileDeletedCount == after.FileDeletedCount
|
||||
}
|
||||
|
||||
func findLastAppendAtNsFromCopiedFiles(idxFileName, datFileName string, version needle.Version) (uint64, error) {
|
||||
if version < needle.Version3 {
|
||||
return 0, nil
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
package weed_server
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb/volume_server_pb"
|
||||
)
|
||||
|
||||
func TestCheckCopyCounts(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
sourceFileCount uint64
|
||||
sourceDeleteCount uint64
|
||||
targetFileCount uint64
|
||||
targetDeleteCount uint64
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
name: "empty volume counts match",
|
||||
},
|
||||
{
|
||||
name: "file and deleted counts match",
|
||||
sourceFileCount: 10,
|
||||
sourceDeleteCount: 3,
|
||||
targetFileCount: 10,
|
||||
targetDeleteCount: 3,
|
||||
},
|
||||
{
|
||||
name: "file count differs",
|
||||
sourceFileCount: 10,
|
||||
sourceDeleteCount: 3,
|
||||
targetFileCount: 9,
|
||||
targetDeleteCount: 3,
|
||||
wantErr: "file count",
|
||||
},
|
||||
{
|
||||
name: "deleted count differs",
|
||||
sourceFileCount: 10,
|
||||
sourceDeleteCount: 3,
|
||||
targetFileCount: 10,
|
||||
targetDeleteCount: 2,
|
||||
wantErr: "deleted count",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := checkCopyCounts(
|
||||
&volume_server_pb.VolumeStatusResponse{
|
||||
FileCount: tt.sourceFileCount,
|
||||
FileDeletedCount: tt.sourceDeleteCount,
|
||||
},
|
||||
tt.targetFileCount,
|
||||
tt.targetDeleteCount,
|
||||
)
|
||||
if tt.wantErr == "" {
|
||||
if err != nil {
|
||||
t.Fatalf("checkCopyCounts() error = %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err == nil || !strings.Contains(err.Error(), tt.wantErr) {
|
||||
t.Fatalf("checkCopyCounts() error = %v, want substring %q", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCopyCountsStable(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
before *volume_server_pb.VolumeStatusResponse
|
||||
after *volume_server_pb.VolumeStatusResponse
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "empty volume is stable",
|
||||
before: &volume_server_pb.VolumeStatusResponse{},
|
||||
after: &volume_server_pb.VolumeStatusResponse{},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "matching file and deleted counts are stable",
|
||||
before: &volume_server_pb.VolumeStatusResponse{
|
||||
FileCount: 10,
|
||||
FileDeletedCount: 3,
|
||||
},
|
||||
after: &volume_server_pb.VolumeStatusResponse{
|
||||
FileCount: 10,
|
||||
FileDeletedCount: 3,
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "read-only state changes without count changes are stable",
|
||||
before: &volume_server_pb.VolumeStatusResponse{
|
||||
FileCount: 10,
|
||||
FileDeletedCount: 3,
|
||||
IsReadOnly: true,
|
||||
},
|
||||
after: &volume_server_pb.VolumeStatusResponse{
|
||||
FileCount: 10,
|
||||
FileDeletedCount: 3,
|
||||
IsReadOnly: false,
|
||||
},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "changed file count is not stable",
|
||||
before: &volume_server_pb.VolumeStatusResponse{
|
||||
FileCount: 10,
|
||||
FileDeletedCount: 3,
|
||||
},
|
||||
after: &volume_server_pb.VolumeStatusResponse{
|
||||
FileCount: 9,
|
||||
FileDeletedCount: 3,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "changed deleted count is not stable",
|
||||
before: &volume_server_pb.VolumeStatusResponse{
|
||||
FileCount: 10,
|
||||
FileDeletedCount: 3,
|
||||
},
|
||||
after: &volume_server_pb.VolumeStatusResponse{
|
||||
FileCount: 10,
|
||||
FileDeletedCount: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "missing before status is not stable",
|
||||
after: &volume_server_pb.VolumeStatusResponse{},
|
||||
},
|
||||
{
|
||||
name: "missing after status is not stable",
|
||||
before: &volume_server_pb.VolumeStatusResponse{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := copyCountsStable(tt.before, tt.after); got != tt.want {
|
||||
t.Fatalf("copyCountsStable() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user