mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-09-01 13:47:51 +00:00
* master: cap the reported capacity at what the disks hold Statistics reported max volume count times the volume size limit, which is how many volumes the cluster is allowed to place, not how much space it has. A cluster given far more slots than its disks can fill reported a capacity it could never reach -- 65536 slots at 30GB read as 1.9PB on a 460GB disk -- and the number never moved, since writing data changes neither the slot count nor the size limit. The volume servers already report each filesystem's total and free bytes in their heartbeats, so bound the answer by what they say is left. * mount: keep the last known sizes when filer statistics fails A failed Statistics call returned before df's answer was filled in, so a mount whose filer or master was briefly unreachable reported an empty filesystem rather than the sizes it already had. * master: drop the disk ceiling when a volume server does not report A cluster part way through an upgrade has volume servers that predate the disk bytes in the heartbeat. Summing only the ones that answered left the quiet server's free space out of the total, and the server holding the room is exactly the one that could make the cluster read as full. Answer with the disks only when every one of them reported.
174 lines
5.8 KiB
Go
174 lines
5.8 KiB
Go
package weed_server
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/pb/master_pb"
|
|
"github.com/seaweedfs/seaweedfs/weed/storage/needle"
|
|
"github.com/seaweedfs/seaweedfs/weed/topology"
|
|
)
|
|
|
|
// ecShardSizes returns count shard sizes of size bytes each.
|
|
func ecShardSizes(count int, size int64) []int64 {
|
|
sizes := make([]int64, count)
|
|
for i := range sizes {
|
|
sizes[i] = size
|
|
}
|
|
return sizes
|
|
}
|
|
|
|
// newStatisticsMaster returns a leader master over four nodes of 10 volume
|
|
// slots at 1MB each. Collection c1 holds a 1000 byte volume replicated to two
|
|
// nodes and an EC volume of 14 shards at 100 bytes; collection c2 holds a
|
|
// single 5000 byte volume.
|
|
func newStatisticsMaster(t *testing.T) *MasterServer {
|
|
t.Helper()
|
|
|
|
ms := newLeaderMaster()
|
|
ms.option.VolumeSizeLimitMB = 1
|
|
|
|
rack := ms.Topo.GetOrCreateDataCenter("dc1").GetOrCreateRack("rack1")
|
|
maxVolumeCounts := map[string]uint32{"": 10}
|
|
newNode := func(host string) *topology.DataNode {
|
|
dn := rack.GetOrCreateDataNode(host, 34534, 0, host, "", maxVolumeCounts)
|
|
// VolumeLocationList.Stats only counts nodes connected for over a minute
|
|
dn.LastSeen = time.Now().Unix() - 61
|
|
return dn
|
|
}
|
|
|
|
replicatedVolume := &master_pb.VolumeInformationMessage{
|
|
Id: 1, Size: 1000, Collection: "c1", FileCount: 10,
|
|
ReplicaPlacement: 1, // 001: one copy in the same rack
|
|
Version: uint32(needle.GetCurrentVersion()),
|
|
}
|
|
for _, host := range []string{"127.0.0.1", "127.0.0.2"} {
|
|
ms.Topo.SyncDataNodeRegistration([]*master_pb.VolumeInformationMessage{replicatedVolume}, newNode(host))
|
|
}
|
|
|
|
otherCollectionNode := newNode("127.0.0.3")
|
|
ms.Topo.SyncDataNodeRegistration([]*master_pb.VolumeInformationMessage{
|
|
{Id: 3, Size: 5000, Collection: "c2", FileCount: 50, Version: uint32(needle.GetCurrentVersion())},
|
|
}, otherCollectionNode)
|
|
|
|
// EC shards are not replicated here, so the volume sits on one node
|
|
ms.Topo.SyncDataNodeEcShards([]*master_pb.VolumeEcShardInformationMessage{
|
|
{Id: 2, Collection: "c1", EcIndexBits: 1<<14 - 1, ShardSizes: ecShardSizes(14, 100), FileCount: 7},
|
|
}, newNode("127.0.0.4"))
|
|
|
|
return ms
|
|
}
|
|
|
|
func TestStatisticsLogicalSizes(t *testing.T) {
|
|
ms := newStatisticsMaster(t)
|
|
|
|
resp, err := ms.Statistics(context.Background(), &master_pb.StatisticsRequest{
|
|
Collection: "c1",
|
|
Replication: "001",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Statistics: %v", err)
|
|
}
|
|
|
|
// 1000 bytes on each of two replicas, plus every shard of the EC volume
|
|
if got, want := resp.UsedSize, uint64(2*1000+14*100); got != want {
|
|
t.Errorf("used size: got %d, want %d", got, want)
|
|
}
|
|
// one replica, and the 10 data shards of the EC volume
|
|
if got, want := resp.LogicalUsedSize, uint64(1000+10*100); got != want {
|
|
t.Errorf("logical used size: got %d, want %d", got, want)
|
|
}
|
|
|
|
// four nodes of 10 slots at 1MB each
|
|
totalSize := uint64(40 * 1024 * 1024)
|
|
if resp.TotalSize != totalSize {
|
|
t.Fatalf("total size: got %d, want %d", resp.TotalSize, totalSize)
|
|
}
|
|
// what is left over is what c2 has not taken either, and 001 writes two
|
|
// copies of it
|
|
clusterUsedSize := resp.UsedSize + 5000
|
|
want := resp.LogicalUsedSize + (totalSize-clusterUsedSize)/2
|
|
if resp.LogicalTotalSize != want {
|
|
t.Errorf("logical total size: got %d, want %d", resp.LogicalTotalSize, want)
|
|
}
|
|
}
|
|
|
|
func TestStatisticsReplicaCopyCount(t *testing.T) {
|
|
ms := newStatisticsMaster(t)
|
|
ms.option.DefaultReplicaPlacement = "010"
|
|
|
|
for _, tc := range []struct {
|
|
name string
|
|
replication string
|
|
copies uint64
|
|
}{
|
|
{"requested", "002", 3},
|
|
{"empty falls back to the master default", "", 2},
|
|
{"unparsable falls back to the master default", "not-a-replication", 2},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
resp, err := ms.Statistics(context.Background(), &master_pb.StatisticsRequest{
|
|
Replication: tc.replication,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Statistics: %v", err)
|
|
}
|
|
want := resp.LogicalUsedSize + (resp.TotalSize-resp.UsedSize)/tc.copies
|
|
if resp.LogicalTotalSize != want {
|
|
t.Errorf("logical total size: got %d, want %d for %d copies",
|
|
resp.LogicalTotalSize, want, tc.copies)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// reportDiskBytes has the first nodeCount nodes report the same filesystem
|
|
// capacity, the way a volume server does in its heartbeat.
|
|
func reportDiskBytes(ms *MasterServer, nodeCount int, totalBytes, freeBytes uint64) {
|
|
rack := ms.Topo.GetOrCreateDataCenter("dc1").GetOrCreateRack("rack1")
|
|
for _, node := range rack.Children()[:nodeCount] {
|
|
node.(*topology.DataNode).AdjustDiskUsageBytes(
|
|
map[string]uint64{"": totalBytes}, map[string]uint64{"": freeBytes})
|
|
}
|
|
}
|
|
|
|
// TestStatisticsPhysicalCapacity covers a cluster configured with more volume
|
|
// slots than its disks hold. Slots promise 40MB here; what the disks can still
|
|
// take is what gets reported.
|
|
func TestStatisticsPhysicalCapacity(t *testing.T) {
|
|
ms := newStatisticsMaster(t)
|
|
|
|
slotCapacity := uint64(40 * 1024 * 1024)
|
|
statistics := func() *master_pb.StatisticsResponse {
|
|
t.Helper()
|
|
resp, err := ms.Statistics(context.Background(), &master_pb.StatisticsRequest{})
|
|
if err != nil {
|
|
t.Fatalf("Statistics: %v", err)
|
|
}
|
|
return resp
|
|
}
|
|
|
|
if got := statistics().TotalSize; got != slotCapacity {
|
|
t.Fatalf("disks that report nothing: got %d, want %d", got, slotCapacity)
|
|
}
|
|
|
|
// a volume server too old to report leaves the whole cluster on its slots,
|
|
// since the room it holds would otherwise go missing
|
|
reportDiskBytes(ms, 3, 8<<20, 1<<20)
|
|
if got := statistics().TotalSize; got != slotCapacity {
|
|
t.Errorf("one disk of four reporting nothing: got %d, want %d", got, slotCapacity)
|
|
}
|
|
|
|
reportDiskBytes(ms, 4, 8<<20, 1<<20)
|
|
resp := statistics()
|
|
if want := resp.UsedSize + (4 << 20); resp.TotalSize != want {
|
|
t.Errorf("four disks with 1MB free: got %d, want %d", resp.TotalSize, want)
|
|
}
|
|
|
|
reportDiskBytes(ms, 4, 1<<30, 1<<30)
|
|
if got := statistics().TotalSize; got != slotCapacity {
|
|
t.Errorf("disks roomier than the slots: got %d, want %d", got, slotCapacity)
|
|
}
|
|
}
|