master: accept volume-server Ping targets on follower masters (#9614)

cluster.check asks every master to ping every volume server, but the
Ping gate validated volume-server targets only against the local
topology. Only the leader receives volume-server heartbeats, so a
follower's topology is empty and every probe through it failed with
"unknown ping target ... of type volumeServer".

Fall back to the volume-server set the master learns over its own
MasterClient subscription to the leader, the same source the filer gate
already trusts. The anti-SSRF intent is preserved: Ping still only dials
recognized cluster members.
This commit is contained in:
Chris Lu
2026-05-21 10:19:59 -07:00
committed by GitHub
parent 5b42287c22
commit 9021225591
2 changed files with 42 additions and 3 deletions
+10 -3
View File
@@ -170,10 +170,17 @@ func (ms *MasterServer) isKnownPingTarget(ctx context.Context, target string, ta
case cluster.FilerType, cluster.BrokerType, cluster.S3Type:
return ms.Cluster.IsKnownNode(targetType, addr)
case cluster.VolumeServerType:
if ms.Topo == nil {
return false
if ms.Topo != nil && ms.Topo.LookupDataNodeByAddress(addr) != nil {
return true
}
return ms.Topo.LookupDataNodeByAddress(addr) != nil
// Only the leader receives volume-server heartbeats, so a follower's
// topology is empty. Fall back to the volume-server set the master
// learns over its own MasterClient subscription to the leader, the
// same source the filer gate trusts.
if ms.MasterClient != nil {
return ms.MasterClient.HasVolumeServer(addr)
}
return false
case cluster.MasterType:
if ms.option != nil && ms.option.Master.Equals(addr) {
return true
@@ -8,6 +8,8 @@ import (
"github.com/seaweedfs/seaweedfs/weed/pb"
"github.com/seaweedfs/seaweedfs/weed/sequence"
"github.com/seaweedfs/seaweedfs/weed/topology"
"github.com/seaweedfs/seaweedfs/weed/wdclient"
"google.golang.org/grpc"
)
func TestMasterIsKnownPingTarget(t *testing.T) {
@@ -62,6 +64,36 @@ func TestMasterIsKnownPingTarget(t *testing.T) {
}
}
// On a follower master the topology is empty because only the leader receives
// volume-server heartbeats. The gate must then fall back to the volume-server
// set learned over the MasterClient subscription instead of rejecting every
// volume-server target. The positive MasterClient.HasVolumeServer lookup is
// covered by wdclient.TestHasVolumeServer; here we lock in that an empty
// topology no longer short-circuits to false and that the fallback is
// nil-safe.
func TestMasterIsKnownPingTargetFollowerEmptyTopology(t *testing.T) {
ctx := context.Background()
vs := "10.0.0.10:8080.18080"
// No topology and no MasterClient: nothing to consult, so reject.
bare := &MasterServer{option: &MasterOption{Master: pb.ServerAddress("10.0.0.1:9333")}}
if bare.isKnownPingTarget(ctx, vs, cluster.VolumeServerType) {
t.Fatalf("volume server must be unknown without topology or MasterClient")
}
// Empty topology plus a MasterClient that has not learned this volume
// server yet: still reject, but exercise the MasterClient fallback path.
mc := wdclient.NewMasterClient(grpc.EmptyDialOption{}, "", cluster.MasterType, "", "", "", pb.ServerDiscovery{})
follower := &MasterServer{
option: &MasterOption{Master: pb.ServerAddress("10.0.0.1:9333")},
Topo: topology.NewTopology("test", sequence.NewMemorySequencer(), 32*1024, 5, false),
MasterClient: mc,
}
if follower.isKnownPingTarget(ctx, vs, cluster.VolumeServerType) {
t.Fatalf("volume server must be unknown until the MasterClient learns it")
}
}
func TestVolumeServerIsKnownPingTarget(t *testing.T) {
seed := pb.ServerAddress("10.0.0.1:9333")
vs := &VolumeServer{