ec.shard.unmount: treat same-node shard copies as one target (#11778)

A shard held on two disks of one server produced two identical live
ShardRefs, so resolution reported "ambiguous" even for a fully
node-qualified -shardId. Ambiguity now means the shard lives on more
than one node; duplicate refs on the same node resolve to it.
This commit is contained in:
Chris Lu authored and GitHub committed 2026-10-16 23:45:10 -07:00
1 parent 3c6700c310
commit 3e55971e0e
2 files changed
+85 -1

No files matched your search

+4 -1
View File
@@ -135,7 +135,10 @@ func (c *ecShardUnmounter) doShardsUnmount() error {
for _, ts := range liveShards {
if ts.ShardID == ps.ShardID {
if ps.NodeAddress == "" || ps.NodeAddress == ts.NodeAddress {
if result != nil {
// One node is one unmount target no matter how many of
// its disks hold the shard; ambiguity means the shard
// lives on more than one node.
if result != nil && result.NodeAddress != ts.NodeAddress {
return fmt.Errorf("shard %v is ambiguous", ps)
}
result = ts
+81
View File
@@ -1,8 +1,10 @@
package ec
import (
"bytes"
"testing"
"github.com/seaweedfs/seaweedfs/weed/pb/master_pb"
"github.com/seaweedfs/seaweedfs/weed/storage/erasure_coding"
"github.com/stretchr/testify/assert"
)
@@ -100,3 +102,82 @@ func TestEcShardStringRoundTrips(t *testing.T) {
assert.Equal(t, []*ShardRef{s}, got)
}
}
func unmountTestTopology(nodes map[string]map[string][]uint32) *master_pb.TopologyInfo {
var dataNodes []*master_pb.DataNodeInfo
for addr, disks := range nodes {
diskInfos := map[string]*master_pb.DiskInfo{}
for disk, shardIds := range disks {
si := erasure_coding.NewShardsInfo()
for _, sid := range shardIds {
si.Set(erasure_coding.NewShardInfo(erasure_coding.ShardId(sid), 1))
}
diskInfos[disk] = &master_pb.DiskInfo{
Type: "hdd",
MaxVolumeCount: 100,
EcShardInfos: []*master_pb.VolumeEcShardInformationMessage{{
Id: 100,
Collection: "c1",
EcIndexBits: si.Bitmap(),
ShardSizes: si.SizesInt64(),
DiskType: "hdd",
}},
}
}
dataNodes = append(dataNodes, &master_pb.DataNodeInfo{
Id: addr,
Address: addr,
DiskInfos: diskInfos,
})
}
return &master_pb.TopologyInfo{DataCenterInfos: []*master_pb.DataCenterInfo{{
Id: "dc1",
RackInfos: []*master_pb.RackInfo{{Id: "r1", DataNodeInfos: dataNodes}},
}}}
}
// A shard held on two disks of one node is a single unmount target: the
// unmount RPC addresses the node, not the disk.
func TestUnmountResolvesShardDuplicatedOnOneNode(t *testing.T) {
topo := unmountTestTopology(map[string]map[string][]uint32{
"vs3:8080": {"hdd:0": {12}, "hdd:1": {12}},
"vs4:8080": {"hdd:0": {13}},
})
for _, shards := range [][]*ShardRef{
{{ShardID: 12}},
{{ShardID: 12, NodeAddress: "vs3:8080"}},
} {
var out bytes.Buffer
err := UnmountShards(&Env{}, &out, ShardUnmountRequest{
Topology: topo,
VolumeID: 100,
Shards: shards,
})
assert.NoError(t, err, "shard %+v", shards)
assert.Contains(t, out.String(), "Will unmount 1 shard(s)")
}
}
func TestUnmountStillRejectsShardOnTwoNodes(t *testing.T) {
topo := unmountTestTopology(map[string]map[string][]uint32{
"vs3:8080": {"hdd:0": {12}},
"vs4:8080": {"hdd:0": {12}},
})
var out bytes.Buffer
err := UnmountShards(&Env{}, &out, ShardUnmountRequest{
Topology: topo,
VolumeID: 100,
Shards: []*ShardRef{{ShardID: 12}},
})
assert.ErrorContains(t, err, "ambiguous")
out.Reset()
err = UnmountShards(&Env{}, &out, ShardUnmountRequest{
Topology: topo,
VolumeID: 100,
Shards: []*ShardRef{{ShardID: 12, NodeAddress: "vs3:8080"}},
})
assert.NoError(t, err)
}