mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-11-08 21:08:27 +01:00
fix(shell): honor explicit fs.mergeVolumes from/to direction (#10159)
* fix(shell): honor explicit fs.mergeVolumes from/to direction
mergeVolumes only ever merged a smaller volume into a larger one. When the
user named both -fromVolumeId and -toVolumeId with the source larger than the
target, the planner produced an empty plan and the command printed just
"max volume size: N MB" and moved nothing.
Build the requested pair directly when both ids are given, instead of routing
through the size-descending heuristic. Read-only, empty, and wrong-collection
endpoints are rejected with a clear error rather than a silent no-op.
* fix(shell): allow fs.mergeVolumes into an empty target volume
Merging chunks into an empty volume is valid, e.g. consolidating data into a
freshly created or recently vacuumed volume. Only reject an empty source, which
has nothing to move.
* fix(shell): reject self-map in directed mergeVolumes planner
createMergePlan with from == to returned a {vid: vid} self-merge when called
directly. Guard it in the planner so it is correct independent of the Do
entrypoint.
This commit is contained in:
1 parent
424cd164e9
commit
a653a7f72a
2 files changed
+162
No files matched your search
@@ -357,6 +357,14 @@ func (c *commandFsMergeVolumes) reloadVolumesInfo(masterClient *wdclient.MasterC
|
||||
}
|
||||
|
||||
func (c *commandFsMergeVolumes) createMergePlan(collection string, toVolumeId needle.VolumeId, fromVolumeId needle.VolumeId) (map[needle.VolumeId]needle.VolumeId, error) {
|
||||
// When the user names both endpoints, honor that exact direction. The
|
||||
// heuristic below only ever merges a smaller volume into a larger one, so
|
||||
// an explicit "merge larger into smaller" request would otherwise yield an
|
||||
// empty plan and silently do nothing.
|
||||
if fromVolumeId != 0 && toVolumeId != 0 {
|
||||
return c.createDirectedMergePlan(collection, fromVolumeId, toVolumeId)
|
||||
}
|
||||
|
||||
plan := make(map[needle.VolumeId]needle.VolumeId)
|
||||
volumeIds := maps.Keys(c.volumes)
|
||||
sort.Slice(volumeIds, func(a, b int) bool {
|
||||
@@ -418,6 +426,35 @@ func (c *commandFsMergeVolumes) createMergePlan(collection string, toVolumeId ne
|
||||
return plan, nil
|
||||
}
|
||||
|
||||
// createDirectedMergePlan builds the single-pair plan {from: to} exactly as the
|
||||
// user requested, skipping the smaller-into-larger ordering the heuristic
|
||||
// planner uses. Compatibility and the combined size limit are already checked
|
||||
// by Do before this runs; here we only reject endpoints that cannot
|
||||
// participate (read-only, empty, or outside the requested collection).
|
||||
func (c *commandFsMergeVolumes) createDirectedMergePlan(collection string, from, to needle.VolumeId) (map[needle.VolumeId]needle.VolumeId, error) {
|
||||
if from == to {
|
||||
return nil, fmt.Errorf("no volume id changes, %d == %d", from, to)
|
||||
}
|
||||
for _, vid := range []needle.VolumeId{from, to} {
|
||||
volume, err := c.getVolumeInfoById(vid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if volume.GetReadOnly() {
|
||||
return nil, fmt.Errorf("volume %d is readonly", vid)
|
||||
}
|
||||
if collection != "*" && collection != volume.GetCollection() {
|
||||
return nil, fmt.Errorf("volume %d is not in collection %q", vid, collection)
|
||||
}
|
||||
// Merging into an empty target is valid (e.g. a freshly vacuumed
|
||||
// volume); only an empty source has nothing to move.
|
||||
if vid == from && c.getVolumeSize(volume) == 0 {
|
||||
return nil, fmt.Errorf("volume %d is empty", vid)
|
||||
}
|
||||
}
|
||||
return map[needle.VolumeId]needle.VolumeId{from: to}, nil
|
||||
}
|
||||
|
||||
func (c *commandFsMergeVolumes) getVolumeSizeBasedOnPlan(plan map[needle.VolumeId]needle.VolumeId, vid needle.VolumeId) uint64 {
|
||||
size := c.getVolumeSizeById(vid)
|
||||
for src, dest := range plan {
|
||||
|
||||
Reference in new issue
Block a user