mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-10-17 11:38:35 +02:00
feat: add collection pattern to delete empty volumes (#10129)
* feat: add collection pattern to delete empty volumes Co-authored-by: Codex <noreply@openai.com> * shell: match collection pattern with wildcard matcher Use wildcard.MatchesWildcard in the shared collection-pattern helper, matching command_volume_fix_replication's matchCollectionPattern. The flag only advertises '*' and '?', which is exactly what the matcher supports. --------- Co-authored-by: Codex <noreply@openai.com> Co-authored-by: Chris Lu <chris.lu@gmail.com>
This commit is contained in:
3 files changed
+64
-12
No files matched your search
@@ -27,6 +27,7 @@ func (c *commandVolumeDeleteEmpty) Help() string {
|
||||
return `delete empty volumes from all volume servers
|
||||
|
||||
volume.deleteEmpty -quietFor=24h -apply
|
||||
volume.deleteEmpty -collectionPattern=important* -quietFor=24h -apply
|
||||
|
||||
This command deletes all empty volumes from one volume server.
|
||||
|
||||
@@ -41,6 +42,7 @@ func (c *commandVolumeDeleteEmpty) Do(args []string, commandEnv *CommandEnv, wri
|
||||
|
||||
volDeleteCommand := flag.NewFlagSet(c.Name(), flag.ContinueOnError)
|
||||
quietPeriod := volDeleteCommand.Duration("quietFor", 24*time.Hour, "select empty volumes with no recent writes, avoid newly created ones")
|
||||
collectionPattern := volDeleteCommand.String("collectionPattern", "", "match with wildcard characters '*' and '?'")
|
||||
applyBalancing := volDeleteCommand.Bool("apply", false, "apply to delete empty volumes")
|
||||
// TODO: remove this alias
|
||||
applyBalancingAlias := volDeleteCommand.Bool("force", false, "apply to delete empty volumes (alias for -apply)")
|
||||
@@ -67,7 +69,7 @@ func (c *commandVolumeDeleteEmpty) Do(args []string, commandEnv *CommandEnv, wri
|
||||
eachDataNode(topologyInfo, func(dc DataCenterId, rack RackId, dn *master_pb.DataNodeInfo) {
|
||||
for _, diskInfo := range dn.DiskInfos {
|
||||
for _, v := range diskInfo.VolumeInfos {
|
||||
if v.Size <= super_block.SuperBlockSize && v.ModifiedAtSecond > 0 && v.ModifiedAtSecond+quietSeconds < nowUnixSeconds {
|
||||
if isEmptyVolumeDeleteCandidate(v, quietSeconds, nowUnixSeconds, *collectionPattern) {
|
||||
if *applyBalancing {
|
||||
log.Printf("deleting empty volume %d from %s", v.Id, dn.Id)
|
||||
if deleteErr := deleteVolume(commandEnv.option.GrpcDialOption, needle.VolumeId(v.Id),
|
||||
@@ -85,3 +87,10 @@ func (c *commandVolumeDeleteEmpty) Do(args []string, commandEnv *CommandEnv, wri
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func isEmptyVolumeDeleteCandidate(v *master_pb.VolumeInformationMessage, quietSeconds, nowUnixSeconds int64, collectionPattern string) bool {
|
||||
return matchesVolumeCollectionPattern(collectionPattern, v.Collection) &&
|
||||
v.Size <= super_block.SuperBlockSize &&
|
||||
v.ModifiedAtSecond > 0 &&
|
||||
v.ModifiedAtSecond+quietSeconds < nowUnixSeconds
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package shell
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb/master_pb"
|
||||
)
|
||||
|
||||
func TestIsEmptyVolumeDeleteCandidateCollectionPattern(t *testing.T) {
|
||||
now := int64(1000)
|
||||
quietSeconds := int64(100)
|
||||
quietEmptyVolume := &master_pb.VolumeInformationMessage{
|
||||
Size: 0,
|
||||
ModifiedAtSecond: now - quietSeconds - 1,
|
||||
Collection: "important-logs",
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
pattern string
|
||||
collection string
|
||||
want bool
|
||||
}{
|
||||
{name: "empty pattern matches named collection", pattern: "", collection: "important-logs", want: true},
|
||||
{name: "wildcard matches collection", pattern: "important*", collection: "important-logs", want: true},
|
||||
{name: "wildcard rejects collection", pattern: "important*", collection: "other-logs", want: false},
|
||||
{name: "default pattern matches empty collection", pattern: CollectionDefault, collection: "", want: true},
|
||||
{name: "default pattern rejects named collection", pattern: CollectionDefault, collection: "important-logs", want: false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
v := *quietEmptyVolume
|
||||
v.Collection = tt.collection
|
||||
if got := isEmptyVolumeDeleteCandidate(&v, quietSeconds, now, tt.pattern); got != tt.want {
|
||||
t.Fatalf("isEmptyVolumeDeleteCandidate(collection=%q, pattern=%q) = %v, want %v",
|
||||
tt.collection, tt.pattern, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"sort"
|
||||
"strings"
|
||||
@@ -15,6 +14,7 @@ import (
|
||||
"github.com/seaweedfs/seaweedfs/weed/storage/erasure_coding"
|
||||
"github.com/seaweedfs/seaweedfs/weed/storage/types"
|
||||
"github.com/seaweedfs/seaweedfs/weed/util"
|
||||
"github.com/seaweedfs/seaweedfs/weed/util/wildcard"
|
||||
|
||||
"io"
|
||||
)
|
||||
@@ -319,16 +319,8 @@ func (c *commandVolumeList) isNotMatchDiskInfo(readOnly bool, collection string,
|
||||
if *c.writable && (readOnly || volumeSize == -1 || (c.volumeSizeLimitMb > 0 && uint64(volumeSize) >= c.volumeSizeLimitMb*util.MiByte)) {
|
||||
return true
|
||||
}
|
||||
if *c.collectionPattern != "" {
|
||||
var matched bool
|
||||
if *c.collectionPattern == CollectionDefault {
|
||||
matched = (collection == "")
|
||||
} else {
|
||||
matched, _ = filepath.Match(*c.collectionPattern, collection)
|
||||
}
|
||||
if !matched {
|
||||
return true
|
||||
}
|
||||
if !matchesVolumeCollectionPattern(*c.collectionPattern, collection) {
|
||||
return true
|
||||
}
|
||||
if *c.volumeId > 0 && *c.volumeId != uint64(volumeId) {
|
||||
return true
|
||||
@@ -336,6 +328,16 @@ func (c *commandVolumeList) isNotMatchDiskInfo(readOnly bool, collection string,
|
||||
return false
|
||||
}
|
||||
|
||||
func matchesVolumeCollectionPattern(pattern, collection string) bool {
|
||||
if pattern == "" {
|
||||
return true
|
||||
}
|
||||
if pattern == CollectionDefault {
|
||||
return collection == ""
|
||||
}
|
||||
return wildcard.MatchesWildcard(pattern, collection)
|
||||
}
|
||||
|
||||
func (c *commandVolumeList) writeDiskInfo(writer io.Writer, t *master_pb.DiskInfo, verbosityLevel int, outNodeInfo func()) statistics {
|
||||
var s statistics
|
||||
diskType := t.Type
|
||||
|
||||
Reference in new issue
Block a user