package weed_server import ( "context" "errors" "fmt" "os" "strings" "time" "github.com/seaweedfs/seaweedfs/weed/operation" "github.com/seaweedfs/seaweedfs/weed/stats" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "github.com/seaweedfs/seaweedfs/weed/pb" "github.com/seaweedfs/seaweedfs/weed/security" "github.com/seaweedfs/seaweedfs/weed/storage/backend" "github.com/seaweedfs/seaweedfs/weed/storage/erasure_coding" "github.com/seaweedfs/seaweedfs/weed/storage/needle" "github.com/seaweedfs/seaweedfs/weed/glog" "github.com/seaweedfs/seaweedfs/weed/pb/master_pb" "github.com/seaweedfs/seaweedfs/weed/util" ) func (vs *VolumeServer) GetMaster(ctx context.Context) pb.ServerAddress { return vs.getCurrentMaster() } // lookupRaftLeaderMaster resolves the raft leader for topology mutations via // GetMasterConfiguration on configured peers. It does not update currentMaster; // the heartbeat loop owns that field and reconnects when the stream reports a // different leader than the connected peer. func (vs *VolumeServer) lookupRaftLeaderMaster(ctx context.Context) (pb.ServerAddress, error) { if ctx == nil { ctx = context.Background() } leader, err := operation.LookupRaftLeaderMaster(ctx, vs.SeedMasterNodes, vs.grpcDialOption) if err != nil { if isContextDoneErr(err) { glog.V(1).Infof("volume server %s:%d: raft leader lookup: %v", vs.store.Ip, vs.store.Port, err) } else { glog.V(0).Infof("volume server %s:%d: raft leader lookup: %v", vs.store.Ip, vs.store.Port, err) } return "", err } current := vs.getCurrentMaster() if !leader.Equals(current) { glog.V(1).Infof("volume server %s:%d: raft leader %v (heartbeat peer %v)", vs.store.Ip, vs.store.Port, leader, current) } return leader, nil } // refreshLeaderFromPeers publishes the leader the configured masters report, // so lookups stop going to a master the heartbeat just lost while the loop // retries. Without an answer within timeout no leader is published: the lost // master is dead or no longer leader, and a follower answers lookups from its // own cache rather than refusing them, so failing fast is the safer answer. func (vs *VolumeServer) refreshLeaderFromPeers(timeout time.Duration) { ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() leader, err := vs.lookupRaftLeaderMaster(ctx) if err != nil { vs.setCurrentMaster("") return } vs.setCurrentMaster(leader) } func isContextDoneErr(err error) bool { if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) { return true } if st, ok := status.FromError(err); ok { return st.Code() == codes.Canceled || st.Code() == codes.DeadlineExceeded } return false } // getCurrentMaster returns vs.currentMaster under a read lock so callers // (e.g. Ping admission) do not race with the heartbeat goroutine that // rewrites it on leader changes. func (vs *VolumeServer) getCurrentMaster() pb.ServerAddress { vs.currentMasterLock.RLock() defer vs.currentMasterLock.RUnlock() return vs.currentMaster } // setCurrentMaster updates vs.currentMaster under a write lock. The // heartbeat goroutine calls this only with a master known to be the leader: // one that answered a heartbeat as leader, or the one a follower named, on // the heartbeat stream or, after the stream failed, in its configuration. // After a failed stream with no leader named, it is cleared to "". func (vs *VolumeServer) setCurrentMaster(master pb.ServerAddress) { vs.currentMasterLock.Lock() vs.currentMaster = master vs.currentMasterLock.Unlock() } func (vs *VolumeServer) checkWithMaster() (err error) { for { for _, master := range vs.SeedMasterNodes { err = operation.WithMasterServerClient(context.Background(), false, master, vs.grpcDialOption, func(masterClient master_pb.SeaweedClient) error { resp, err := masterClient.GetMasterConfiguration(context.Background(), &master_pb.GetMasterConfigurationRequest{}) if err != nil { return fmt.Errorf("get master %s configuration: %v", master, err) } vs.metricsAddress, vs.metricsIntervalSec = resp.MetricsAddress, int(resp.MetricsIntervalSeconds) backend.LoadFromPbStorageBackends(resp.StorageBackends) return nil }) if err == nil { return } else { glog.V(0).Infof("checkWithMaster %s: %v", master, err) } } time.Sleep(1790 * time.Millisecond) } } func (vs *VolumeServer) heartbeat() { glog.V(0).Infof("Volume server start with seed master nodes: %v", vs.SeedMasterNodes) vs.store.SetDataCenter(vs.dataCenter) vs.store.SetRack(vs.rack) grpcDialOption := security.LoadClientTLS(util.GetViper(), "grpc.volume") var err error var newLeader pb.ServerAddress duplicateRetryCount := 0 for vs.isHeartbeating { for _, master := range vs.SeedMasterNodes { if newLeader != "" { // the new leader may actually is the same master // need to wait a bit before adding itself time.Sleep(3 * time.Second) master = newLeader } newLeader, err = vs.doHeartbeatWithRetry(master, grpcDialOption, vs.pulsePeriod, duplicateRetryCount) if err != nil { glog.V(0).Infof("heartbeat to %s error: %v", master, err) // Check if this is a duplicate UUID retry error if strings.Contains(err.Error(), "duplicate UUIDs detected, retrying connection") { duplicateRetryCount++ retryDelay := time.Duration(1<<(duplicateRetryCount-1)) * 2 * time.Second // exponential backoff: 2s, 4s, 8s glog.V(0).Infof("Waiting %v before retrying due to duplicate UUID detection...", retryDelay) time.Sleep(retryDelay) } else { // Regular error, reset duplicate retry count duplicateRetryCount = 0 // The lookup spends part of the retry delay rather than // adding to it. retryAt := time.Now().Add(vs.pulsePeriod) vs.refreshLeaderFromPeers(vs.pulsePeriod) time.Sleep(time.Until(retryAt)) } stats.VolumeServerMasterDisconnections.WithLabelValues(master.String()).Inc() newLeader = "" } else { // Successful connection, reset retry count duplicateRetryCount = 0 } if !vs.isHeartbeating { break } } } } func (vs *VolumeServer) StopHeartbeat() (isAlreadyStopping bool) { if !vs.isHeartbeating { return true } vs.isHeartbeating = false close(vs.stopChan) return false } func (vs *VolumeServer) doHeartbeatWithRetry(masterAddress pb.ServerAddress, grpcDialOption grpc.DialOption, sleepInterval time.Duration, duplicateRetryCount int) (newLeader pb.ServerAddress, err error) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() grpcConnection, err := pb.GrpcDial(ctx, masterAddress.ToGrpcAddress(), false, grpcDialOption) if err != nil { return "", fmt.Errorf("fail to dial %s : %v", masterAddress, err) } defer grpcConnection.Close() client := master_pb.NewSeaweedClient(grpcConnection) stream, err := client.SendHeartbeat(ctx) if err != nil { glog.V(0).Infof("SendHeartbeat to %s: %v", masterAddress, err) return "", err } glog.V(0).Infof("Heartbeat to: %v", masterAddress) doneChan := make(chan error, 1) // The receiver asks for a fresh heartbeat here rather than sending it, // since a gRPC stream allows only one sender at a time. resendChan := make(chan struct{}, 1) go func() { confirmedLeader := false for { in, err := stream.Recv() if err != nil { doneChan <- err return } // A reply names the leader or comes from it, so it settles where // lookups go before anything else on the reply is handled: a // follower reply carries no other fields, and a leader stays the // lookup target even when it rejects this registration. if in.GetLeader() != "" { // Only a follower names the leader. Publish the named leader right // away, so lookups stop landing on this follower while the loop // reconnects. leader := pb.ServerAddress(in.GetLeader()) if !masterAddress.Equals(leader) { glog.V(0).Infof("Volume Server found a new master newLeader: %v instead of %v", leader, masterAddress) vs.setCurrentMaster(leader) newLeader = leader doneChan <- nil return } } else if !confirmedLeader { // The leader answers every new heartbeat stream without naming a // leader, so this peer is the one lookups should go to. confirmedLeader = true vs.setCurrentMaster(masterAddress) } if len(in.DuplicatedUuids) > 0 { var duplicateDir []string for _, loc := range vs.store.Locations { for _, uuid := range in.DuplicatedUuids { if uuid == loc.DirectoryUuid { duplicateDir = append(duplicateDir, loc.Directory) } } } // Implement retry logic for potential race conditions const maxRetries = 3 if duplicateRetryCount < maxRetries { retryDelay := time.Duration(1<