mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-09-20 13:30:46 +02:00
* feat: throughput limits for replicate, EC shard, and worker-driven moves VolumeCopy was the only rate-limitable transfer; EC shard copies, replica creation, and worker-driven moves all ran at whatever the receiving server's maintenance rate allowed, with no per-operation control. - proto: VolumeEcShardsCopyRequest and the balance / ec_balance task params and configs gain io_byte_per_second; 0 keeps today's behavior (the volume server's own maintenance rate governs). - volume server: VolumeEcShardsCopy throttles with one WriteThrottler per request, shared across the shard, .ecx, .ecj, .vif, and .ecsum copies so the limit caps the transfer as a whole - the same shape as VolumeCopy. - volume_move: ReplicateVolume accepts the limit; EcMoveOptions carries it through MoveEcShards/CopyAndMountEcShards into the copy request, with fake-client tests asserting propagation. - shell: ec.balance gains -ioBytePerSecond; volume.tier.move's replication top-up honors the command's existing -ioBytePerSecond instead of running unthrottled. - worker: balance and ec_balance configs gain io_byte_per_second (surfaced in the admin config schema), carried through detection and plugin job parameters into task params and handed to the shared mover; batch balance jobs inherit the limit from their detection results. The limit is per copy stream, so maxParallelization multiplies the aggregate ceiling. * worker plugins: expose io_byte_per_second in the plugin config and derive it The plugin-driven detection path derives its task Config from the plugin configuration values, and both balance and ec_balance left IoBytePerSecond at zero there - a configured limit silently reverted to the server maintenance rate. Both derive functions now read the field (clamped at zero), and the plugin descriptors expose it with defaults so the configuration form carries it.
118 lines
3.6 KiB
Go
118 lines
3.6 KiB
Go
package volume_move
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/pb"
|
|
"github.com/seaweedfs/seaweedfs/weed/pb/volume_server_pb"
|
|
"github.com/seaweedfs/seaweedfs/weed/storage/erasure_coding"
|
|
)
|
|
|
|
func ecMove(shardIds ...erasure_coding.ShardId) EcShardMove {
|
|
return EcShardMove{
|
|
VolumeId: 7,
|
|
Collection: "c1",
|
|
ShardIds: shardIds,
|
|
Source: srcAddr,
|
|
Target: dstAddr,
|
|
TargetDisk: 2,
|
|
}
|
|
}
|
|
|
|
func dstShards(shardIds ...uint32) []*volume_server_pb.EcShardInfo {
|
|
var infos []*volume_server_pb.EcShardInfo
|
|
for _, sid := range shardIds {
|
|
infos = append(infos, &volume_server_pb.EcShardInfo{VolumeId: 7, ShardId: sid})
|
|
}
|
|
return infos
|
|
}
|
|
|
|
func TestMoveEcShardsSequence(t *testing.T) {
|
|
cluster := newFakeCluster()
|
|
cluster.ecShards[string(dstAddr)] = dstShards(3, 4)
|
|
|
|
err := cluster.mover().MoveEcShards(context.Background(), ecMove(3, 4), EcMoveOptions{IoBytePerSecond: 77})
|
|
if err != nil {
|
|
t.Fatalf("MoveEcShards: %v", err)
|
|
}
|
|
|
|
assertCalls(t, cluster.callList(), []string{
|
|
"dst:8080 VolumeEcShardsCopy",
|
|
"dst:8080 VolumeEcShardsMount",
|
|
"dst:8080 VolumeEcShardsInfo",
|
|
"src:8080 VolumeEcShardsUnmount",
|
|
"src:8080 VolumeEcShardsDelete",
|
|
})
|
|
|
|
copyReq := cluster.ecCopyReqs[0]
|
|
if !copyReq.CopyEcxFile || !copyReq.CopyEcjFile || !copyReq.CopyVifFile || !copyReq.CopyEcsumFile {
|
|
t.Errorf("shard sidecars not all copied: %+v", copyReq)
|
|
}
|
|
if copyReq.DiskId != 2 || copyReq.SourceDataNode != string(srcAddr) || copyReq.Collection != "c1" || copyReq.IoBytePerSecond != 77 {
|
|
t.Errorf("copy request not propagated: %+v", copyReq)
|
|
}
|
|
}
|
|
|
|
func TestMoveEcShardsVerifyFailureKeepsSource(t *testing.T) {
|
|
cluster := newFakeCluster()
|
|
cluster.ecShards[string(dstAddr)] = dstShards(3) // shard 4 didn't register
|
|
|
|
err := cluster.mover().MoveEcShards(context.Background(), ecMove(3, 4), EcMoveOptions{})
|
|
if err == nil || !strings.Contains(err.Error(), "missing EC shard 7.4") {
|
|
t.Fatalf("expected missing-shard error, got: %v", err)
|
|
}
|
|
|
|
for _, call := range cluster.callList() {
|
|
if call == "src:8080 VolumeEcShardsUnmount" || call == "src:8080 VolumeEcShardsDelete" {
|
|
t.Fatalf("source touched despite verification failure: %v", cluster.callList())
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestMoveEcShardsRejectsSameServer(t *testing.T) {
|
|
// The second target is the same server written with an explicit grpc port;
|
|
// the guard must see through the representation difference.
|
|
for _, target := range []pb.ServerAddress{srcAddr, pb.ServerAddress("src:8080.18080")} {
|
|
cluster := newFakeCluster()
|
|
move := ecMove(3)
|
|
move.Target = target
|
|
|
|
err := cluster.mover().MoveEcShards(context.Background(), move, EcMoveOptions{})
|
|
if err == nil || !strings.Contains(err.Error(), "its own server") {
|
|
t.Fatalf("target %q: expected same-server rejection, got: %v", target, err)
|
|
}
|
|
if len(cluster.callList()) != 0 {
|
|
t.Fatalf("target %q: RPCs issued for a rejected move: %v", target, cluster.callList())
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRemoveEcShards(t *testing.T) {
|
|
cluster := newFakeCluster()
|
|
|
|
err := cluster.mover().RemoveEcShards(context.Background(), 7, "c1", srcAddr, []erasure_coding.ShardId{3})
|
|
if err != nil {
|
|
t.Fatalf("RemoveEcShards: %v", err)
|
|
}
|
|
|
|
assertCalls(t, cluster.callList(), []string{
|
|
"src:8080 VolumeEcShardsUnmount",
|
|
"src:8080 VolumeEcShardsDelete",
|
|
})
|
|
}
|
|
|
|
func TestCopyAndMountEcShardsSameAddressMountsOnly(t *testing.T) {
|
|
cluster := newFakeCluster()
|
|
|
|
err := cluster.mover().CopyAndMountEcShards(context.Background(), 7, "c1", []erasure_coding.ShardId{3}, srcAddr, srcAddr, 0, 0, nil)
|
|
if err != nil {
|
|
t.Fatalf("CopyAndMountEcShards: %v", err)
|
|
}
|
|
|
|
assertCalls(t, cluster.callList(), []string{
|
|
"src:8080 VolumeEcShardsMount",
|
|
})
|
|
}
|