Browse Source

Fix #8040: Support '_default' keyword in collectionPattern to match default collection (#8046)

* Fix #8040: Support 'default' keyword in collectionPattern to match default collection

The default collection in SeaweedFS is represented as an empty string internally.
Previously, it was impossible to specifically target only the default collection
because:
- Empty collectionPattern matched ALL collections (filter was skipped)
- Using collectionPattern="default" tried to match the literal string "default"

This commit adds special handling for the keyword "default" in collectionPattern
across multiple shell commands:
- volume.tier.move
- volume.list
- volume.fix.replication
- volume.configure.replication

Now users can use -collectionPattern="default" to specifically target volumes
in the default collection (empty collection name), while maintaining backward
compatibility where empty pattern matches all collections.

Updated help text to document this feature.

* Update compileCollectionPattern to support 'default' keyword

This extends the fix to all commands that use regex-based collection
pattern matching:
- ec.encode
- ec.decode
- volume.tier.download
- volume.balance

The compileCollectionPattern function now treats "default" as a special
keyword that compiles to the regex "^$" (matching empty strings), making
it consistent with the other commands that use filepath.Match.

* Use CollectionDefault constant instead of hardcoded "default" string

Refactored the collection pattern matching logic to use a central constant
CollectionDefault defined in weed/shell/common.go. This improves maintainability
and ensures consistency across all shell commands.

* Address PR review feedback: simplify logic and use '_default' keyword

Changes:
1. Changed CollectionDefault from "default" to "_default" to avoid collision
   with literal collection names
2. Simplified pattern matching logic to reduce code duplication across all
   affected commands
3. Fixed error handling in command_volume_tier_move.go to properly propagate
   filepath.Match errors instead of swallowing them
4. Updated documentation to clarify how to match a literal "default"
   collection using regex patterns like "^default$"

This addresses all feedback from PR review comments.

* Remove unnecessary documentation about matching literal 'default'

Since we changed the keyword to '_default', users can now simply use
'default' to match a literal collection named "default". The previous
documentation about using regex patterns was confusing and no longer needed.

* Fix error propagation and empty pattern handling

1. command_volume_tier_move.go: Added early termination check after
   eachDataNode callback to stop processing remaining nodes if a pattern
   matching error occurred, improving efficiency

2. command_volume_configure_replication.go: Fixed empty pattern handling
   to match all collections (collectionMatched = true when pattern is empty),
   mirroring the behavior in other commands

These changes address the remaining PR review feedback.
pull/8047/head
Chris Lu 3 weeks ago
committed by GitHub
parent
commit
0a46577700
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 5
      weed/shell/command_ec_common.go
  2. 17
      weed/shell/command_volume_configure_replication.go
  3. 24
      weed/shell/command_volume_fix_replication.go
  4. 8
      weed/shell/command_volume_list.go
  5. 22
      weed/shell/command_volume_tier_move.go
  6. 3
      weed/shell/common.go

5
weed/shell/command_ec_common.go

@ -1628,11 +1628,16 @@ func EcBalance(commandEnv *CommandEnv, collections []string, dc string, ecReplic
// compileCollectionPattern compiles a regex pattern for collection matching.
// Empty patterns match empty collections only.
// The special keyword CollectionDefault ("_default") matches empty collections.
func compileCollectionPattern(pattern string) (*regexp.Regexp, error) {
if pattern == "" {
// empty pattern matches empty collection
return regexp.Compile("^$")
}
if pattern == CollectionDefault {
// CollectionDefault keyword matches empty collection
return regexp.Compile("^$")
}
return regexp.Compile(pattern)
}

17
weed/shell/command_volume_configure_replication.go

@ -116,10 +116,19 @@ func getVolumeFilter(replicaPlacement *super_block.ReplicaPlacement, volumeId ui
}
}
return func(v *master_pb.VolumeInformationMessage) bool {
matched, err := filepath.Match(collectionPattern, v.Collection)
if err != nil {
return false
var collectionMatched bool
if collectionPattern == "" {
// Empty pattern matches all collections
collectionMatched = true
} else if collectionPattern == CollectionDefault {
collectionMatched = v.Collection == ""
} else {
m, err := filepath.Match(collectionPattern, v.Collection)
if err != nil {
return false
}
collectionMatched = m
}
return matched
return collectionMatched && v.ReplicaPlacement != replicaPlacementInt32
}
}

24
weed/shell/command_volume_fix_replication.go

@ -268,9 +268,15 @@ func (c *commandVolumeFixReplication) deleteOneVolume(commandEnv *CommandEnv, wr
// check collection name pattern
if *c.collectionPattern != "" {
matched, err := filepath.Match(*c.collectionPattern, replica.info.Collection)
if err != nil {
return fmt.Errorf("match pattern %s with collection %s: %v", *c.collectionPattern, replica.info.Collection, err)
var matched bool
if *c.collectionPattern == CollectionDefault {
matched = replica.info.Collection == ""
} else {
var err error
matched, err = filepath.Match(*c.collectionPattern, replica.info.Collection)
if err != nil {
return fmt.Errorf("match pattern %s with collection %s: %v", *c.collectionPattern, replica.info.Collection, err)
}
}
if !matched {
continue
@ -357,9 +363,15 @@ func (c *commandVolumeFixReplication) fixOneUnderReplicatedVolume(commandEnv *Co
if fn(dst.dataNode) > 0 && satisfyReplicaPlacement(replicaPlacement, replicas, dst) {
// check collection name pattern
if *c.collectionPattern != "" {
matched, err := filepath.Match(*c.collectionPattern, replica.info.Collection)
if err != nil {
return fmt.Errorf("match pattern %s with collection %s: %v", *c.collectionPattern, replica.info.Collection, err)
var matched bool
if *c.collectionPattern == CollectionDefault {
matched = replica.info.Collection == ""
} else {
var err error
matched, err = filepath.Match(*c.collectionPattern, replica.info.Collection)
if err != nil {
return fmt.Errorf("match pattern %s with collection %s: %v", *c.collectionPattern, replica.info.Collection, err)
}
}
if !matched {
hasSkippedCollection = true

8
weed/shell/command_volume_list.go

@ -202,7 +202,13 @@ func (c *commandVolumeList) isNotMatchDiskInfo(readOnly bool, collection string,
return true
}
if *c.collectionPattern != "" {
if matched, _ := filepath.Match(*c.collectionPattern, collection); !matched {
var matched bool
if *c.collectionPattern == CollectionDefault {
matched = (collection == "")
} else {
matched, _ = filepath.Match(*c.collectionPattern, collection)
}
if !matched {
return true
}
}

22
weed/shell/command_volume_tier_move.go

@ -50,6 +50,10 @@ func (c *commandVolumeTierMove) Help() string {
Even if the volume is replicated, only one replica will be changed and the rest replicas will be dropped.
So "volume.fix.replication" and "volume.balance" should be followed.
Note:
Use -collectionPattern="_default" to match only the default collection (volumes with no collection name).
Empty collectionPattern matches all collections.
`
}
@ -310,9 +314,16 @@ func collectVolumeIdsForTierChange(topologyInfo *master_pb.TopologyInfo, volumeS
for _, v := range diskInfo.VolumeInfos {
// check collection name pattern
if collectionPattern != "" {
matched, err := filepath.Match(collectionPattern, v.Collection)
if err != nil {
return
var matched bool
if collectionPattern == CollectionDefault {
matched = v.Collection == ""
} else {
var matchErr error
matched, matchErr = filepath.Match(collectionPattern, v.Collection)
if matchErr != nil {
err = fmt.Errorf("collection pattern %q failed to match: %w", collectionPattern, matchErr)
return
}
}
if !matched {
continue
@ -328,6 +339,11 @@ func collectVolumeIdsForTierChange(topologyInfo *master_pb.TopologyInfo, volumeS
}
})
// Check if an error occurred during iteration and return early
if err != nil {
return
}
for vid := range vidMap {
vids = append(vids, needle.VolumeId(vid))
}

3
weed/shell/common.go

@ -9,6 +9,9 @@ import (
var (
// Default maximum parallelization/concurrency for commands supporting it.
DefaultMaxParallelization = 10
// CollectionDefault is the special keyword to match empty collection names.
// Use "_default" to avoid collision with a literal collection named "default".
CollectionDefault = "_default"
)
// ErrorWaitGroup implements a goroutine wait group which aggregates errors, if any.

Loading…
Cancel
Save