Browse Source
feat(plugin): enhanced collection filtering for volume balance (#8620)
feat(plugin): enhanced collection filtering for volume balance (#8620)
* feat(plugin): enhanced collection filtering for volume balance Replace wildcard matching with three collection filter modes: - ALL_COLLECTIONS (default): treat all volumes as one pool - EACH_COLLECTION: run detection separately per collection - Regex pattern: filter volumes by matching collection names The EACH_COLLECTION mode extracts distinct collections from metrics and calls Detection() per collection, sharing the maxResults budget and clusterInfo (with ActiveTopology) across all calls. * address PR review: fix wildcard→regexp replacement, optimize EACH_COLLECTION * address nitpick: fail fast on config errors (invalid regex) Add configError type so invalid collection_filter regex returns immediately instead of retrying across all masters with the same bad config. Transient errors still retry. * address review: constants, unbounded maxResults, wildcard compat - Define collectionFilterAll/collectionFilterEach constants to eliminate magic strings across handler and metrics code - Fix EACH_COLLECTION budget loop to treat maxResults <= 0 as unbounded, matching Detection's existing semantics - Treat "*" as ALL_COLLECTIONS for backward compat with wildcard * address review: nil guard in EACH_COLLECTION grouping loop * remove useless descriptor string testpull/8626/head
committed by
GitHub
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 194 additions and 8 deletions
-
59weed/plugin/worker/volume_balance_handler.go
-
34weed/plugin/worker/volume_metrics.go
-
109weed/plugin/worker/volume_metrics_test.go
@ -0,0 +1,109 @@ |
|||
package pluginworker |
|||
|
|||
import ( |
|||
"testing" |
|||
|
|||
"github.com/seaweedfs/seaweedfs/weed/pb/master_pb" |
|||
) |
|||
|
|||
func makeTestVolumeListResponse(volumes ...*master_pb.VolumeInformationMessage) *master_pb.VolumeListResponse { |
|||
return &master_pb.VolumeListResponse{ |
|||
VolumeSizeLimitMb: 30000, |
|||
TopologyInfo: &master_pb.TopologyInfo{ |
|||
DataCenterInfos: []*master_pb.DataCenterInfo{ |
|||
{ |
|||
Id: "dc1", |
|||
RackInfos: []*master_pb.RackInfo{ |
|||
{ |
|||
Id: "rack1", |
|||
DataNodeInfos: []*master_pb.DataNodeInfo{ |
|||
{ |
|||
Id: "server1:8080", |
|||
DiskInfos: map[string]*master_pb.DiskInfo{ |
|||
"hdd": { |
|||
VolumeInfos: volumes, |
|||
}, |
|||
}, |
|||
}, |
|||
}, |
|||
}, |
|||
}, |
|||
}, |
|||
}, |
|||
}, |
|||
} |
|||
} |
|||
|
|||
func TestBuildVolumeMetricsEmptyFilter(t *testing.T) { |
|||
resp := makeTestVolumeListResponse( |
|||
&master_pb.VolumeInformationMessage{Id: 1, Collection: "photos", Size: 100}, |
|||
&master_pb.VolumeInformationMessage{Id: 2, Collection: "videos", Size: 200}, |
|||
) |
|||
metrics, _, err := buildVolumeMetrics(resp, "") |
|||
if err != nil { |
|||
t.Fatalf("unexpected error: %v", err) |
|||
} |
|||
if len(metrics) != 2 { |
|||
t.Fatalf("expected 2 metrics, got %d", len(metrics)) |
|||
} |
|||
} |
|||
|
|||
func TestBuildVolumeMetricsAllCollections(t *testing.T) { |
|||
resp := makeTestVolumeListResponse( |
|||
&master_pb.VolumeInformationMessage{Id: 1, Collection: "photos", Size: 100}, |
|||
&master_pb.VolumeInformationMessage{Id: 2, Collection: "videos", Size: 200}, |
|||
) |
|||
metrics, _, err := buildVolumeMetrics(resp, collectionFilterAll) |
|||
if err != nil { |
|||
t.Fatalf("unexpected error: %v", err) |
|||
} |
|||
if len(metrics) != 2 { |
|||
t.Fatalf("expected 2 metrics, got %d", len(metrics)) |
|||
} |
|||
} |
|||
|
|||
func TestBuildVolumeMetricsEachCollection(t *testing.T) { |
|||
resp := makeTestVolumeListResponse( |
|||
&master_pb.VolumeInformationMessage{Id: 1, Collection: "photos", Size: 100}, |
|||
&master_pb.VolumeInformationMessage{Id: 2, Collection: "videos", Size: 200}, |
|||
) |
|||
// EACH_COLLECTION passes all volumes through; filtering happens in the handler
|
|||
metrics, _, err := buildVolumeMetrics(resp, collectionFilterEach) |
|||
if err != nil { |
|||
t.Fatalf("unexpected error: %v", err) |
|||
} |
|||
if len(metrics) != 2 { |
|||
t.Fatalf("expected 2 metrics, got %d", len(metrics)) |
|||
} |
|||
} |
|||
|
|||
func TestBuildVolumeMetricsRegexFilter(t *testing.T) { |
|||
resp := makeTestVolumeListResponse( |
|||
&master_pb.VolumeInformationMessage{Id: 1, Collection: "photos", Size: 100}, |
|||
&master_pb.VolumeInformationMessage{Id: 2, Collection: "videos", Size: 200}, |
|||
&master_pb.VolumeInformationMessage{Id: 3, Collection: "photos-backup", Size: 300}, |
|||
) |
|||
metrics, _, err := buildVolumeMetrics(resp, "^photos$") |
|||
if err != nil { |
|||
t.Fatalf("unexpected error: %v", err) |
|||
} |
|||
if len(metrics) != 1 { |
|||
t.Fatalf("expected 1 metric, got %d", len(metrics)) |
|||
} |
|||
if metrics[0].Collection != "photos" { |
|||
t.Fatalf("expected collection 'photos', got %q", metrics[0].Collection) |
|||
} |
|||
} |
|||
|
|||
func TestBuildVolumeMetricsInvalidRegex(t *testing.T) { |
|||
resp := makeTestVolumeListResponse( |
|||
&master_pb.VolumeInformationMessage{Id: 1, Collection: "photos", Size: 100}, |
|||
) |
|||
_, _, err := buildVolumeMetrics(resp, "[invalid") |
|||
if err == nil { |
|||
t.Fatal("expected error for invalid regex") |
|||
} |
|||
if !isConfigError(err) { |
|||
t.Fatalf("expected config error for invalid regex, got: %v", err) |
|||
} |
|||
} |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue