mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-08-30 12:47:00 +00:00
* wdclient: bound the wait for a master leader by the caller's context WithClient waited on GetMaster with context.Background(), so a caller that arrived while no master leader was known parked in a 200ms poll loop until one appeared, whatever deadline it had already set on the RPC. Each retry above it then left another goroutine in the same wait. Take the context in WithClient and WithClientCustomGetMaster and hand it to GetMaster, and stop the retry loop once it is done. The dial keeps context.Background(): fn brings its own RPC context, so a cancellation seen here cannot be attributed to the shared connection. Call sites pass whatever they hold: the request context in the filer's CollectionList, DeleteCollection and Statistics handlers and in the credential store's propagation, the operation context in the shell's s3.bucket.delete and the kafka gateway's broker and filer discovery, and context.Background() where there is none - the shell commands, the admin dashboard wrapper, and the exclusive locker's initial lease. The locker's release keeps its own uncancelled context so a slow unlock cannot turn into a ghost lock. Claude-Session: https://claude.ai/code/session_01BjDWtZsCoZY6x4pdDmGWxU * wdclient: test that WithClient gives up with the caller's context Claude-Session: https://claude.ai/code/session_01BjDWtZsCoZY6x4pdDmGWxU * wdclient: cut the master retry backoff short when the caller gives up util.Retry sleeps unconditionally between attempts, so a transient error arriving just before the caller's deadline still cost it a full backoff step. Use the context-aware util.RetryWithBackoff, the same helper the volume lookup in this file already uses. Two call sites went with it: the shell's lock-holder lookup builds its three second bound before WithClient so it also covers finding the leader, as its comment already promised, and the filer's post-delete collection cleanup goes back to an uncancelled context - the entry is already gone, so a caller that hung up must not leave the collection behind. Claude-Session: https://claude.ai/code/session_01BjDWtZsCoZY6x4pdDmGWxU * wdclient: test that a cancel during backoff ends the retry Claude-Session: https://claude.ai/code/session_01BjDWtZsCoZY6x4pdDmGWxU
250 lines
8.0 KiB
Go
250 lines
8.0 KiB
Go
package shell
|
|
|
|
import (
|
|
"context"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
|
|
"github.com/seaweedfs/seaweedfs/weed/cluster"
|
|
"github.com/seaweedfs/seaweedfs/weed/pb"
|
|
"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
|
|
"github.com/seaweedfs/seaweedfs/weed/pb/master_pb"
|
|
"github.com/seaweedfs/seaweedfs/weed/pb/volume_server_pb"
|
|
"github.com/seaweedfs/seaweedfs/weed/storage/types"
|
|
)
|
|
|
|
func init() {
|
|
Commands = append(Commands, &commandClusterCheck{})
|
|
}
|
|
|
|
type commandClusterCheck struct {
|
|
}
|
|
|
|
func (c *commandClusterCheck) Name() string {
|
|
return "cluster.check"
|
|
}
|
|
|
|
func (c *commandClusterCheck) Help() string {
|
|
return `check current cluster network connectivity
|
|
|
|
cluster.check
|
|
|
|
`
|
|
}
|
|
|
|
func (c *commandClusterCheck) HasTag(CommandTag) bool {
|
|
return false
|
|
}
|
|
|
|
func (c *commandClusterCheck) Do(args []string, commandEnv *CommandEnv, writer io.Writer) (err error) {
|
|
|
|
clusterPsCommand := flag.NewFlagSet(c.Name(), flag.ContinueOnError)
|
|
if err = clusterPsCommand.Parse(args); err != nil {
|
|
return nil
|
|
}
|
|
|
|
// collect topology information
|
|
topologyInfo, volumeSizeLimitMb, err := collectTopologyInfo(commandEnv, 0)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fmt.Fprintf(writer, "Topology volumeSizeLimit:%d MB%s\n", volumeSizeLimitMb, diskInfosToString(topologyInfo.DiskInfos))
|
|
|
|
if len(topologyInfo.DiskInfos) == 0 {
|
|
return fmt.Errorf("no disk type defined")
|
|
}
|
|
for diskType, diskInfo := range topologyInfo.DiskInfos {
|
|
if diskInfo.MaxVolumeCount == 0 {
|
|
return fmt.Errorf("no volume available for \"%s\" disk type", diskType)
|
|
}
|
|
}
|
|
|
|
// collect filers
|
|
var filers []pb.ServerAddress
|
|
err = commandEnv.MasterClient.WithClient(context.Background(), false, func(client master_pb.SeaweedClient) error {
|
|
resp, err := client.ListClusterNodes(context.Background(), &master_pb.ListClusterNodesRequest{
|
|
ClientType: cluster.FilerType,
|
|
FilerGroup: *commandEnv.option.FilerGroup,
|
|
})
|
|
|
|
for _, node := range resp.ClusterNodes {
|
|
filers = append(filers, pb.ServerAddress(node.Address))
|
|
}
|
|
return err
|
|
})
|
|
if err != nil {
|
|
return
|
|
}
|
|
fmt.Fprintf(writer, "the cluster has %d filers: %+v\n", len(filers), filers)
|
|
|
|
if len(filers) > 0 {
|
|
genericDiskInfo, genericDiskInfoOk := topologyInfo.DiskInfos[""]
|
|
hddDiskInfo, hddDiskInfoOk := topologyInfo.DiskInfos[types.HddType]
|
|
|
|
if !genericDiskInfoOk && !hddDiskInfoOk {
|
|
return fmt.Errorf("filer metadata logs need generic or hdd disk type to be defined")
|
|
}
|
|
|
|
if (genericDiskInfoOk && genericDiskInfo.MaxVolumeCount == 0) || (hddDiskInfoOk && hddDiskInfo.MaxVolumeCount == 0) {
|
|
return fmt.Errorf("filer metadata logs need generic or hdd volumes to be available")
|
|
}
|
|
}
|
|
|
|
// collect volume servers
|
|
var volumeServers []pb.ServerAddress
|
|
t, _, err := collectTopologyInfo(commandEnv, 0)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, dc := range t.DataCenterInfos {
|
|
for _, r := range dc.RackInfos {
|
|
for _, dn := range r.DataNodeInfos {
|
|
volumeServers = append(volumeServers, pb.NewServerAddressFromDataNode(dn))
|
|
}
|
|
}
|
|
}
|
|
fmt.Fprintf(writer, "the cluster has %d volume servers: %+v\n", len(volumeServers), volumeServers)
|
|
|
|
// collect all masters
|
|
var masters []pb.ServerAddress
|
|
masters = append(masters, commandEnv.MasterClient.GetMasters(context.Background())...)
|
|
|
|
// check from master to volume servers
|
|
for _, master := range masters {
|
|
for _, volumeServer := range volumeServers {
|
|
fmt.Fprintf(writer, "checking master %s to volume server %s ... ", string(master), string(volumeServer))
|
|
err := pb.WithMasterClient(context.Background(), false, master, commandEnv.option.GrpcDialOption, false, func(client master_pb.SeaweedClient) error {
|
|
pong, err := client.Ping(context.Background(), &master_pb.PingRequest{
|
|
Target: string(volumeServer),
|
|
TargetType: cluster.VolumeServerType,
|
|
})
|
|
if err == nil {
|
|
printTiming(writer, pong.StartTimeNs, pong.RemoteTimeNs, pong.StopTimeNs)
|
|
}
|
|
return err
|
|
})
|
|
if err != nil {
|
|
fmt.Fprintf(writer, "%v\n", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// check between masters
|
|
for _, sourceMaster := range masters {
|
|
for _, targetMaster := range masters {
|
|
if sourceMaster == targetMaster {
|
|
continue
|
|
}
|
|
fmt.Fprintf(writer, "checking master %s to %s ... ", string(sourceMaster), string(targetMaster))
|
|
err := pb.WithMasterClient(context.Background(), false, sourceMaster, commandEnv.option.GrpcDialOption, false, func(client master_pb.SeaweedClient) error {
|
|
pong, err := client.Ping(context.Background(), &master_pb.PingRequest{
|
|
Target: string(targetMaster),
|
|
TargetType: cluster.MasterType,
|
|
})
|
|
if err == nil {
|
|
printTiming(writer, pong.StartTimeNs, pong.RemoteTimeNs, pong.StopTimeNs)
|
|
}
|
|
return err
|
|
})
|
|
if err != nil {
|
|
fmt.Fprintf(writer, "%v\n", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// check from volume servers to masters
|
|
for _, volumeServer := range volumeServers {
|
|
for _, master := range masters {
|
|
fmt.Fprintf(writer, "checking volume server %s to master %s ... ", string(volumeServer), string(master))
|
|
err := pb.WithVolumeServerClient(false, volumeServer, commandEnv.option.GrpcDialOption, func(client volume_server_pb.VolumeServerClient) error {
|
|
pong, err := client.Ping(context.Background(), &volume_server_pb.PingRequest{
|
|
Target: string(master),
|
|
TargetType: cluster.MasterType,
|
|
})
|
|
if err == nil {
|
|
printTiming(writer, pong.StartTimeNs, pong.RemoteTimeNs, pong.StopTimeNs)
|
|
}
|
|
return err
|
|
})
|
|
if err != nil {
|
|
fmt.Fprintf(writer, "%v\n", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// check from filers to masters
|
|
for _, filer := range filers {
|
|
for _, master := range masters {
|
|
fmt.Fprintf(writer, "checking filer %s to master %s ... ", string(filer), string(master))
|
|
err := pb.WithFilerClient(false, 0, filer, commandEnv.option.GrpcDialOption, func(client filer_pb.SeaweedFilerClient) error {
|
|
pong, err := client.Ping(context.Background(), &filer_pb.PingRequest{
|
|
Target: string(master),
|
|
TargetType: cluster.MasterType,
|
|
})
|
|
if err == nil {
|
|
printTiming(writer, pong.StartTimeNs, pong.RemoteTimeNs, pong.StopTimeNs)
|
|
}
|
|
return err
|
|
})
|
|
if err != nil {
|
|
fmt.Fprintf(writer, "%v\n", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// check from filers to volume servers
|
|
for _, filer := range filers {
|
|
for _, volumeServer := range volumeServers {
|
|
fmt.Fprintf(writer, "checking filer %s to volume server %s ... ", string(filer), string(volumeServer))
|
|
err := pb.WithFilerClient(false, 0, filer, commandEnv.option.GrpcDialOption, func(client filer_pb.SeaweedFilerClient) error {
|
|
pong, err := client.Ping(context.Background(), &filer_pb.PingRequest{
|
|
Target: string(volumeServer),
|
|
TargetType: cluster.VolumeServerType,
|
|
})
|
|
if err == nil {
|
|
printTiming(writer, pong.StartTimeNs, pong.RemoteTimeNs, pong.StopTimeNs)
|
|
}
|
|
return err
|
|
})
|
|
if err != nil {
|
|
fmt.Fprintf(writer, "%v\n", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Direct volume-to-volume connectivity is intentionally not validated
|
|
// here. Each volume server now restricts Ping to peers it can identify
|
|
// (its configured/current masters), so it does not carry a peer-volume
|
|
// list to drive a mesh check from. The master->volume and filer->volume
|
|
// probes above do not exercise volume-to-volume reachability.
|
|
|
|
// check between filers, and need to connect to itself
|
|
for _, sourceFiler := range filers {
|
|
for _, targetFiler := range filers {
|
|
fmt.Fprintf(writer, "checking filer %s to %s ... ", string(sourceFiler), string(targetFiler))
|
|
err := pb.WithFilerClient(false, 0, sourceFiler, commandEnv.option.GrpcDialOption, func(client filer_pb.SeaweedFilerClient) error {
|
|
pong, err := client.Ping(context.Background(), &filer_pb.PingRequest{
|
|
Target: string(targetFiler),
|
|
TargetType: cluster.FilerType,
|
|
})
|
|
if err == nil {
|
|
printTiming(writer, pong.StartTimeNs, pong.RemoteTimeNs, pong.StopTimeNs)
|
|
}
|
|
return err
|
|
})
|
|
if err != nil {
|
|
fmt.Fprintf(writer, "%v\n", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func printTiming(writer io.Writer, startNs, remoteNs, stopNs int64) {
|
|
roundTripTimeMs := float32(stopNs-startNs) / 1000000
|
|
deltaTimeMs := float32(remoteNs-(startNs+stopNs)/2) / 1000000
|
|
fmt.Fprintf(writer, "ok round trip %.3fms clock delta %.3fms\n", roundTripTimeMs, deltaTimeMs)
|
|
}
|