Files
seaweedfs/weed/shell/command_volume_delete_empty_test.go
T
Chris LuandClaude Opus 5.5 440c50fc65 shell: delete an empty volume only when every copy is empty
volume.deleteEmpty judged each copy on its own, so it removed an empty
copy while another copy still held data or was offline, leaving that
one as the volume's only copy. Low-disk copies now report
readOnlyCanDelete and passed the check as well. Apply the master
sweep's rule: every copy is listed and every copy is empty.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 11:58:53 -08:00

85 lines
4.1 KiB
Go

package shell
import (
"testing"
"github.com/seaweedfs/seaweedfs/weed/pb/master_pb"
"github.com/seaweedfs/seaweedfs/weed/storage/super_block"
"github.com/seaweedfs/seaweedfs/weed/util/wildcard"
"github.com/stretchr/testify/assert"
)
func TestIsEmptyVolumeDeleteCandidate(t *testing.T) {
matcher, err := wildcard.CompileCollectionMatcher("")
assert.NoError(t, err)
const quietSeconds = int64(60)
const now = int64(1_000_000)
old := now - quietSeconds - 1
tests := []struct {
name string
v *master_pb.VolumeInformationMessage
want bool
}{
{"small .dat is empty", &master_pb.VolumeInformationMessage{Size: super_block.SuperBlockSize, ModifiedAtSecond: old}, true},
{"all needles deleted is empty", &master_pb.VolumeInformationMessage{Size: 1 << 30, FileCount: 100, DeleteCount: 100, ModifiedAtSecond: old}, true},
{"live needles keep the volume", &master_pb.VolumeInformationMessage{Size: 1 << 30, FileCount: 100, DeleteCount: 99, ModifiedAtSecond: old}, false},
{"overwrites alone are not empty", &master_pb.VolumeInformationMessage{Size: 1 << 30, FileCount: 200, DeleteCount: 100, ModifiedAtSecond: old}, false},
{"recent all-deleted volume is kept", &master_pb.VolumeInformationMessage{Size: 1 << 30, FileCount: 100, DeleteCount: 100, ModifiedAtSecond: now}, false},
{"never written volume is kept", &master_pb.VolumeInformationMessage{Size: super_block.SuperBlockSize, ModifiedAtSecond: 0}, false},
{"remote-backed replica is kept", &master_pb.VolumeInformationMessage{Size: super_block.SuperBlockSize, RemoteStorageName: "s3", RemoteStorageKey: "v", ModifiedAtSecond: old}, false},
{"remote-backed garbage is kept", &master_pb.VolumeInformationMessage{Size: 1 << 30, FileCount: 100, DeleteCount: 100, RemoteStorageName: "s3", ModifiedAtSecond: old}, false},
{"protected read-only volume is kept", &master_pb.VolumeInformationMessage{Size: super_block.SuperBlockSize, ReadOnly: true, ModifiedAtSecond: old}, false},
{"deletable read-only volume can go", &master_pb.VolumeInformationMessage{Size: super_block.SuperBlockSize, ReadOnly: true, ReadOnlyCanDelete: true, ModifiedAtSecond: old}, true},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
assert.Equal(t, tc.want, isEmptyVolumeDeleteCandidate(tc.v, quietSeconds, now, matcher))
})
}
}
func TestIsEmptyVolumeCopiesDeleteCandidate(t *testing.T) {
matcher, err := wildcard.CompileCollectionMatcher("")
assert.NoError(t, err)
const quietSeconds = int64(60)
const now = int64(1_000_000)
old := now - quietSeconds - 1
copyOf := func(rp uint32, v *master_pb.VolumeInformationMessage) *VolumeReplica {
v.ReplicaPlacement = rp
return &VolumeReplica{location: &location{dataNode: &master_pb.DataNodeInfo{Id: "dn"}}, info: v}
}
empty := func() *master_pb.VolumeInformationMessage {
return &master_pb.VolumeInformationMessage{Size: super_block.SuperBlockSize, ModifiedAtSecond: old}
}
lowDisk := func() *master_pb.VolumeInformationMessage {
return &master_pb.VolumeInformationMessage{Size: super_block.SuperBlockSize, ReadOnly: true, ReadOnlyCanDelete: true, ModifiedAtSecond: old}
}
withData := func() *master_pb.VolumeInformationMessage {
return &master_pb.VolumeInformationMessage{Size: 1 << 20, FileCount: 3, ModifiedAtSecond: old}
}
protected := func() *master_pb.VolumeInformationMessage {
return &master_pb.VolumeInformationMessage{Size: super_block.SuperBlockSize, ReadOnly: true, ModifiedAtSecond: old}
}
tests := []struct {
name string
replicas []*VolumeReplica
want bool
}{
{"single copy volume", []*VolumeReplica{copyOf(0, empty())}, true},
{"every copy empty", []*VolumeReplica{copyOf(1, empty()), copyOf(1, lowDisk())}, true},
{"other copy holds data", []*VolumeReplica{copyOf(1, lowDisk()), copyOf(1, withData())}, false},
{"other copy is missing", []*VolumeReplica{copyOf(1, lowDisk())}, false},
{"other copy is protected read-only", []*VolumeReplica{copyOf(1, empty()), copyOf(1, protected())}, false},
{"no copies", nil, false},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
assert.Equal(t, tc.want, isEmptyVolumeCopiesDeleteCandidate(tc.replicas, quietSeconds, now, matcher))
})
}
}