From d9786c240ced9613b690f67938e3ab473d595ab9 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Thu, 12 Nov 2026 18:46:42 -0800 Subject: [PATCH] shell: name the hdd disk type when copying a downloaded volume VolumeCopy treats an empty disk type as the source's, so an HDD replica copied from an SSD source was placed on SSD or failed. Co-Authored-By: Claude Opus 5.5 --- weed/shell/command_volume_tier_download.go | 3 ++- .../command_volume_tier_download_test.go | 19 +++++++++++++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/weed/shell/command_volume_tier_download.go b/weed/shell/command_volume_tier_download.go index e614e64665..ea5103ea2c 100644 --- a/weed/shell/command_volume_tier_download.go +++ b/weed/shell/command_volume_tier_download.go @@ -15,6 +15,7 @@ import ( "github.com/seaweedfs/seaweedfs/weed/pb/master_pb" "github.com/seaweedfs/seaweedfs/weed/pb/volume_server_pb" "github.com/seaweedfs/seaweedfs/weed/storage/needle" + "github.com/seaweedfs/seaweedfs/weed/storage/types" ) func init() { @@ -220,7 +221,7 @@ func downloadAndReplicateFromRemoteTier(grpcDialOption grpc.DialOption, writer i func replicateTierDownloadedVolume(grpcDialOption grpc.DialOption, writer io.Writer, vid needle.VolumeId, source pb.ServerAddress, target tierDownloadReplica) error { fmt.Fprintf(writer, "replicate downloaded volume %d from %s to %s\n", vid, source, target.server) - if _, err := copyVolume(context.Background(), grpcDialOption, writer, vid, source, target.server, target.diskType, 0, true); err != nil { + if _, err := copyVolume(context.Background(), grpcDialOption, writer, vid, source, target.server, types.ToDiskType(target.diskType).ReadableString(), 0, true); err != nil { return fmt.Errorf("replicate volume %d from %s to %s: %v", vid, source, target.server, err) } return nil diff --git a/weed/shell/command_volume_tier_download_test.go b/weed/shell/command_volume_tier_download_test.go index 88c42531bf..c08b54fc5b 100644 --- a/weed/shell/command_volume_tier_download_test.go +++ b/weed/shell/command_volume_tier_download_test.go @@ -16,6 +16,7 @@ import ( "github.com/seaweedfs/seaweedfs/weed/pb/master_pb" "github.com/seaweedfs/seaweedfs/weed/pb/volume_server_pb" "github.com/seaweedfs/seaweedfs/weed/storage/needle" + "github.com/seaweedfs/seaweedfs/weed/storage/types" ) const ( @@ -88,8 +89,13 @@ func (s *tierDownloadTestServer) VolumeCopy(req *volume_server_pb.VolumeCopyRequ c := s.cluster c.mu.Lock() defer c.mu.Unlock() - if req.DiskType != c.diskTypes[s.addr] { - return fmt.Errorf("copy volume %d onto disk type %q, replica is on %q", req.VolumeId, req.DiskType, c.diskTypes[s.addr]) + // an empty disk type means the source's, as on the real servers + want, got := types.ToDiskType(c.diskTypes[s.addr]), types.ToDiskType(c.diskTypes[pb.ServerAddress(req.SourceDataNode)]) + if req.DiskType != "" { + got = types.ToDiskType(req.DiskType) + } + if got != want { + return fmt.Errorf("copy volume %d onto disk type %q, replica is on %q", req.VolumeId, got, want) } switch c.replicas[pb.ServerAddress(req.SourceDataNode)] { case tierReplicaLocal: @@ -262,3 +268,12 @@ func TestTierDownloadRerunReusesLocalReplica(t *testing.T) { t.Errorf("want no download when a replica is already local, got %d", c.downloads) } } + +func TestTierDownloadCopiesFromSsdSourceOntoHddReplica(t *testing.T) { + c := newTierDownloadCluster(t, tierReplicaRemote, tierReplicaRemote, tierReplicaRemote) + c.hide(0, c.servers[0]) + if err := c.run(t); err != nil { + t.Fatalf("tier download: %v", err) + } + c.assertAllLocal(t) +}