mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-10-09 15:57:47 +02:00
VolumeLocationList.Stats subtracts the deleted figures from the totals to report live size and needle count. Both deleted figures are maintained as counters independent of the totals they come off, so either can transiently exceed its total, and neither subtraction was clamped. Unclamped, the size wraps to ~16 EB. The count is signed so it merely goes negative, but VolumeLayout.Stats converts it with uint64(fileCount), which turns it into ~1.8e19 just the same. Either one swamps the cluster totals behind /dir/status, /vol/status and Topology.CollectionVolumeStats. commandFsMergeVolumes.getVolumeSize had the same unclamped subtraction, where a wrapped size reads as a volume far too large to join any merge plan. Clamped to zero, matching the guards already in CollectionInfo.LogicalSize and the admin server's logical-size accumulator.
141 lines
4.8 KiB
Go
141 lines
4.8 KiB
Go
package shell
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/pb/master_pb"
|
|
"github.com/seaweedfs/seaweedfs/weed/storage/needle"
|
|
)
|
|
|
|
func newMergeCmd(limitMB uint64, vols ...*master_pb.VolumeInformationMessage) *commandFsMergeVolumes {
|
|
c := &commandFsMergeVolumes{
|
|
volumes: make(map[needle.VolumeId]*master_pb.VolumeInformationMessage),
|
|
volumeSizeLimit: limitMB * 1024 * 1024,
|
|
}
|
|
for _, v := range vols {
|
|
c.volumes[needle.VolumeId(v.Id)] = v
|
|
}
|
|
return c
|
|
}
|
|
|
|
// An explicit -fromVolumeId/-toVolumeId pair must be honored as given, even when
|
|
// the source is larger than the target. The heuristic planner only ever merges
|
|
// a smaller volume into a larger one, which used to make this request a silent
|
|
// no-op.
|
|
func TestCreateMergePlan_HonorsExplicitDirection(t *testing.T) {
|
|
larger := &master_pb.VolumeInformationMessage{Id: 87, Size: 7192590976}
|
|
smaller := &master_pb.VolumeInformationMessage{Id: 83, Size: 7088822248}
|
|
c := newMergeCmd(250000, larger, smaller)
|
|
|
|
plan, err := c.createMergePlan("*", needle.VolumeId(83), needle.VolumeId(87))
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if got := plan[needle.VolumeId(87)]; got != needle.VolumeId(83) {
|
|
t.Fatalf("expected 87->83, got plan=%v", plan)
|
|
}
|
|
|
|
// The reverse direction keeps working too.
|
|
plan, err = c.createMergePlan("*", needle.VolumeId(87), needle.VolumeId(83))
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if got := plan[needle.VolumeId(83)]; got != needle.VolumeId(87) {
|
|
t.Fatalf("expected 83->87, got plan=%v", plan)
|
|
}
|
|
}
|
|
|
|
// Merging into an empty target is valid (e.g. consolidating into a freshly
|
|
// vacuumed volume); only an empty source should be rejected.
|
|
func TestCreateMergePlan_DirectedAllowsEmptyTarget(t *testing.T) {
|
|
from := &master_pb.VolumeInformationMessage{Id: 87, Size: 100}
|
|
emptyTo := &master_pb.VolumeInformationMessage{Id: 83, Size: 0}
|
|
c := newMergeCmd(250000, from, emptyTo)
|
|
|
|
plan, err := c.createMergePlan("*", needle.VolumeId(83), needle.VolumeId(87))
|
|
if err != nil {
|
|
t.Fatalf("unexpected error merging into empty target: %v", err)
|
|
}
|
|
if got := plan[needle.VolumeId(87)]; got != needle.VolumeId(83) {
|
|
t.Fatalf("expected 87->83, got plan=%v", plan)
|
|
}
|
|
}
|
|
|
|
// The directed planner rejects a self-map on its own, not just via Do, since it
|
|
// is exercised directly.
|
|
func TestCreateMergePlan_DirectedRejectsSelfMap(t *testing.T) {
|
|
v := &master_pb.VolumeInformationMessage{Id: 87, Size: 100}
|
|
c := newMergeCmd(250000, v)
|
|
|
|
_, err := c.createMergePlan("*", needle.VolumeId(87), needle.VolumeId(87))
|
|
if err == nil || !strings.Contains(err.Error(), "no volume id changes") {
|
|
t.Fatalf("expected self-map rejection, got %v", err)
|
|
}
|
|
}
|
|
|
|
func TestCreateMergePlan_DirectedRejectsIneligible(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
from, to *master_pb.VolumeInformationMessage
|
|
coll string
|
|
wantErr string
|
|
}{
|
|
{
|
|
name: "readonly source",
|
|
from: &master_pb.VolumeInformationMessage{Id: 87, Size: 100, ReadOnly: true},
|
|
to: &master_pb.VolumeInformationMessage{Id: 83, Size: 100},
|
|
coll: "*",
|
|
wantErr: "volume 87 is readonly",
|
|
},
|
|
{
|
|
name: "readonly target",
|
|
from: &master_pb.VolumeInformationMessage{Id: 87, Size: 100},
|
|
to: &master_pb.VolumeInformationMessage{Id: 83, Size: 100, ReadOnly: true},
|
|
coll: "*",
|
|
wantErr: "volume 83 is readonly",
|
|
},
|
|
{
|
|
name: "empty source",
|
|
from: &master_pb.VolumeInformationMessage{Id: 87, Size: 0},
|
|
to: &master_pb.VolumeInformationMessage{Id: 83, Size: 100},
|
|
coll: "*",
|
|
wantErr: "volume 87 is empty",
|
|
},
|
|
{
|
|
name: "wrong collection",
|
|
from: &master_pb.VolumeInformationMessage{Id: 87, Size: 100, Collection: "other"},
|
|
to: &master_pb.VolumeInformationMessage{Id: 83, Size: 100, Collection: "other"},
|
|
coll: "wanted",
|
|
wantErr: "volume 87 is not in collection",
|
|
},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
c := newMergeCmd(250000, tc.from, tc.to)
|
|
_, err := c.createMergePlan(tc.coll, needle.VolumeId(tc.to.Id), needle.VolumeId(tc.from.Id))
|
|
if err == nil {
|
|
t.Fatalf("expected error %q, got nil", tc.wantErr)
|
|
}
|
|
if got := err.Error(); !strings.Contains(got, tc.wantErr) {
|
|
t.Fatalf("expected error containing %q, got %q", tc.wantErr, got)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// A volume reporting more deleted bytes than it holds must read as empty.
|
|
func TestGetVolumeSize_ClampsDeletedOverSize(t *testing.T) {
|
|
c := newMergeCmd(250000)
|
|
|
|
overDeleted := &master_pb.VolumeInformationMessage{Id: 1, Size: 1000, DeletedByteCount: 4000}
|
|
if got := c.getVolumeSize(overDeleted); got != 0 {
|
|
t.Errorf("expected 0 for a volume with more deleted than stored, got %d", got)
|
|
}
|
|
|
|
healthy := &master_pb.VolumeInformationMessage{Id: 2, Size: 3000, DeletedByteCount: 1000}
|
|
if got := c.getVolumeSize(healthy); got != 2000 {
|
|
t.Errorf("expected 2000, got %d", got)
|
|
}
|
|
}
|