mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-10-07 06:47:51 +02:00
feat: add collection.mark shell command
Add collection.mark to mark all existing normal volume replicas in a collection as readonly or writable. The command runs in preview mode by default and requires -apply to execute changes. It reuses existing volume mark RPCs, supports default collection aliases, skips EC shards, and adds unit tests for option parsing and target collection logic.
This commit is contained in:
2 files changed
+314
No files matched your search
@@ -0,0 +1,148 @@
|
||||
package shell
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/seaweedfs/seaweedfs/weed/pb/master_pb"
|
||||
)
|
||||
|
||||
func TestCollectionMarkNormalizeCollectionName(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
in string
|
||||
want string
|
||||
}{
|
||||
{name: "named collection", in: "photos", want: "photos"},
|
||||
{name: "default keyword", in: "_default", want: ""},
|
||||
{name: "delete command default keyword", in: "_default_", want: ""},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := normalizeCollectionMarkName(tt.in)
|
||||
if got != tt.want {
|
||||
t.Fatalf("normalizeCollectionMarkName(%q) = %q, want %q", tt.in, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCollectionMarkParseOptions(t *testing.T) {
|
||||
opts, err := parseCollectionMarkOptions([]string{"-collection", "photos", "-readonly"})
|
||||
if err != nil {
|
||||
t.Fatalf("parse readonly options: %v", err)
|
||||
}
|
||||
if opts.collection != "photos" {
|
||||
t.Fatalf("collection = %q, want photos", opts.collection)
|
||||
}
|
||||
if opts.markWritable {
|
||||
t.Fatalf("markWritable = true, want false for -readonly")
|
||||
}
|
||||
if opts.apply {
|
||||
t.Fatalf("apply = true, want false without -apply")
|
||||
}
|
||||
|
||||
opts, err = parseCollectionMarkOptions([]string{"-collection", "_default_", "-writable", "-apply"})
|
||||
if err != nil {
|
||||
t.Fatalf("parse writable options: %v", err)
|
||||
}
|
||||
if opts.collection != "" {
|
||||
t.Fatalf("collection = %q, want empty default collection", opts.collection)
|
||||
}
|
||||
if !opts.markWritable {
|
||||
t.Fatalf("markWritable = false, want true for -writable")
|
||||
}
|
||||
if !opts.apply {
|
||||
t.Fatalf("apply = false, want true with -apply")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCollectionMarkParseOptionsRequiresCollectionAndSingleMode(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
args []string
|
||||
}{
|
||||
{name: "missing collection", args: []string{"-readonly"}},
|
||||
{name: "missing mode", args: []string{"-collection", "photos"}},
|
||||
{name: "conflicting modes", args: []string{"-collection", "photos", "-readonly", "-writable"}},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if _, err := parseCollectionMarkOptions(tt.args); err == nil {
|
||||
t.Fatalf("parseCollectionMarkOptions(%v) returned nil error", tt.args)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCollectionMarkCollectTargets(t *testing.T) {
|
||||
topo := &master_pb.TopologyInfo{
|
||||
DataCenterInfos: []*master_pb.DataCenterInfo{
|
||||
{
|
||||
RackInfos: []*master_pb.RackInfo{
|
||||
{
|
||||
DataNodeInfos: []*master_pb.DataNodeInfo{
|
||||
{
|
||||
Id: "node-a",
|
||||
Address: "10.0.0.1:8080",
|
||||
GrpcPort: 9080,
|
||||
DiskInfos: map[string]*master_pb.DiskInfo{
|
||||
"hdd": {
|
||||
VolumeInfos: []*master_pb.VolumeInformationMessage{
|
||||
{Id: 3, Collection: "photos", ReadOnly: false},
|
||||
{Id: 4, Collection: "other", ReadOnly: false},
|
||||
},
|
||||
EcShardInfos: []*master_pb.VolumeEcShardInformationMessage{
|
||||
{Id: 9, Collection: "photos"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Id: "node-b",
|
||||
Address: "10.0.0.2:8080",
|
||||
GrpcPort: 9081,
|
||||
DiskInfos: map[string]*master_pb.DiskInfo{
|
||||
"hdd": {
|
||||
VolumeInfos: []*master_pb.VolumeInformationMessage{
|
||||
{Id: 3, Collection: "photos", ReadOnly: true},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
targets, skippedEC := collectCollectionMarkTargets(topo, "photos")
|
||||
if skippedEC != 1 {
|
||||
t.Fatalf("skippedEC = %d, want 1", skippedEC)
|
||||
}
|
||||
if len(targets) != 2 {
|
||||
t.Fatalf("len(targets) = %d, want 2: %#v", len(targets), targets)
|
||||
}
|
||||
|
||||
if got := uint32(targets[0].volumeId); got != 3 {
|
||||
t.Fatalf("target[0].volumeId = %d, want 3", got)
|
||||
}
|
||||
if got := string(targets[0].server); got != "10.0.0.1:8080.9080" {
|
||||
t.Fatalf("target[0].server = %q, want 10.0.0.1:8080.9080", got)
|
||||
}
|
||||
if targets[0].readOnly {
|
||||
t.Fatalf("target[0].readOnly = true, want false")
|
||||
}
|
||||
|
||||
if got := uint32(targets[1].volumeId); got != 3 {
|
||||
t.Fatalf("target[1].volumeId = %d, want 3", got)
|
||||
}
|
||||
if got := string(targets[1].server); got != "10.0.0.2:8080.9081" {
|
||||
t.Fatalf("target[1].server = %q, want 10.0.0.2:8080.9081", got)
|
||||
}
|
||||
if !targets[1].readOnly {
|
||||
t.Fatalf("target[1].readOnly = false, want true")
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user