diff --git a/weed/shell/command_ec_common.go b/weed/shell/command_ec_common.go index a6f714a0d..4c3b7459c 100644 --- a/weed/shell/command_ec_common.go +++ b/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) } diff --git a/weed/shell/command_volume_configure_replication.go b/weed/shell/command_volume_configure_replication.go index c54efd05c..74d20eb93 100644 --- a/weed/shell/command_volume_configure_replication.go +++ b/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 } } diff --git a/weed/shell/command_volume_fix_replication.go b/weed/shell/command_volume_fix_replication.go index 9c760ef9d..574579c34 100644 --- a/weed/shell/command_volume_fix_replication.go +++ b/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 diff --git a/weed/shell/command_volume_list.go b/weed/shell/command_volume_list.go index 04573d402..956d372bb 100644 --- a/weed/shell/command_volume_list.go +++ b/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 } } diff --git a/weed/shell/command_volume_tier_move.go b/weed/shell/command_volume_tier_move.go index cfa2eaef3..b0f9cba12 100644 --- a/weed/shell/command_volume_tier_move.go +++ b/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)) } diff --git a/weed/shell/common.go b/weed/shell/common.go index cb2df5828..922cd5316 100644 --- a/weed/shell/common.go +++ b/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.