test: wait for the master to register the volume servers before failover tests run (#10871)

The failover harness treated an open volume server port as readiness, but the
master only learns of a volume server from its heartbeat. A lone master refuses
heartbeats until its bootstrap check elects it, and the servers back off and
retry, so registration lands seconds after the ports answer. Tests that started
writing in that window assigned against an empty topology, which fails with
"no free volumes left" and reaches the mount as ENOSPC.
This commit is contained in:
Chris Lu authored and GitHub committed 2026-08-21 23:05:14 -07:00
1 parent c0a9b110dd
commit 301d83cc7a
2 files changed
+60 -1

No files matched your search

+57
View File
@@ -109,6 +109,7 @@ func startFailoverCluster(t testing.TB, numVolumes, numMounts int) *failoverClus
require.NoError(t, c.waitForMount(mp, 30*time.Second),
"mount %d not ready\n%s", i, c.tailLog(fmt.Sprintf("mount%d", i)))
}
require.NoError(t, c.WaitForVolumeServers(60*time.Second))
return c
}
@@ -344,6 +345,62 @@ func (c *failoverCluster) WaitForHolders(vid uint32, count int, timeout time.Dur
}
}
// WaitForVolumeServers blocks until the master's topology lists every volume
// server. A volume server accepts connections well before the master knows it
// exists: a lone master only elects itself once its bootstrap check expires,
// and heartbeats sent before that are refused and retried with backoff, so
// registration lands seconds after the process is up. Until it does the
// topology has no data node at all, an assign fails with "no free volumes
// left", and the mount reports that as ENOSPC instead of retrying.
func (c *failoverCluster) WaitForVolumeServers(timeout time.Duration) error {
deadline := time.Now().Add(timeout)
for {
registered := c.registeredVolumeServers()
var missing []string
for i := range c.volumePorts {
if address := c.VolumeServerAddress(i); !registered[address] {
missing = append(missing, address)
}
}
if len(missing) == 0 {
return nil
}
if time.Now().After(deadline) {
return fmt.Errorf("volume servers %v not registered with the master within %v\n%s",
missing, timeout, c.MasterGet("/dir/status?pretty=y"))
}
time.Sleep(200 * time.Millisecond)
}
}
// registeredVolumeServers is the set of volume server addresses the master
// currently holds in its topology.
func (c *failoverCluster) registeredVolumeServers() map[string]bool {
var status struct {
Topology struct {
DataCenters []struct {
Racks []struct {
DataNodes []struct {
Url string `json:"Url"`
} `json:"DataNodes"`
} `json:"Racks"`
} `json:"DataCenters"`
} `json:"Topology"`
}
if err := json.Unmarshal([]byte(c.MasterGet("/dir/status")), &status); err != nil {
return nil
}
registered := make(map[string]bool)
for _, dc := range status.Topology.DataCenters {
for _, rack := range dc.Racks {
for _, dn := range rack.DataNodes {
registered[dn.Url] = true
}
}
}
return registered
}
// volumeIndexOf maps a server address back to its index, or -1.
func (c *failoverCluster) volumeIndexOf(address string) int {
for i := range c.volumePorts {