mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-10-23 06:20:48 +02:00
* volume: route lookups to the leader master only (#11887) After a leader change, the volume server kept sending volume and EC shard lookups to masters that were not the leader: * currentMaster was set as soon as a heartbeat stream opened, before the peer said whether it was leader, and kept a follower through the redirect and its 3s sleep. A follower answers LookupVolume from its own vid cache, which right after an election can be empty, so reads went 404 rather than failing over. * EC lookups used Store.MasterAddress, a second, unlocked copy that the heartbeat loop set before each dial and cleared on errors, so EC and plain reads could disagree on the master or have none. * the receive goroutine called stream.Send itself when the volume options changed, racing the send loop, and exited silently on failure, after which leader redirects were no longer read. Publish currentMaster only once a master is known to be leader: the peer answered a new stream without naming a leader, which the leader always does, or a follower named the leader. Compare redirects against the connected peer. Replace Store.MasterAddress with MasterFn, backed by getCurrentMaster. Have the receiver ask the send loop for the extra heartbeat instead of sending it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * volume: ask the masters for the leader after losing the heartbeat When the heartbeat stream to the leader failed, currentMaster kept the lost master until a new stream answered, so lookups on a cache miss kept dialing it through the retry delay even with a new leader already elected. On a regular heartbeat error, ask the configured masters for the raft leader and publish it, within the retry delay rather than on top of it. Without an answer the last known leader stays: clearing it would only trade a dead address for an empty one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test: stop the heartbeat on a failed assertion The leader test closed stopChan only once every assertion had passed, so a failure left the stream open and the fake master's GracefulStop waited for the whole test run's timeout. Start the heartbeat through a helper whose cleanup stops it, safe to call twice, and waits for it to exit. It runs before the cleanups registered earlier, so the stream is closed before the store and the fake master go away. Both heartbeat tests use it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * volume: clear the leader when no master names one When the heartbeat lost its master and no configured master named a leader, refreshLeaderFromPeers kept the lost one. That master is dead or no longer leader, and a follower answers LookupVolume from its own cache instead of refusing, so lookups could return locations no leader vouched for. Clear currentMaster instead. Lookups then fail fast ("missing address") until a leader is known again, rather than reading a follower's cache or waiting on a dial to a dead master. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * volume: keep volumeSizeLimit 64-bit aligned on 32-bit builds MasterFn (a func, 4 bytes on 386) replaced MasterAddress (a string, 8 bytes) ahead of volumeSizeLimit and moved it to a 4-byte-aligned offset, where atomic.LoadUint64 panics with an unaligned 64-bit operation. The atomic.Uint64 type carries the alignment itself. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * volume: publish the answering leader before a duplicate-uuid retry Read the leader field first on each heartbeat reply. A follower reply carries nothing else, so it no longer runs through the option updates; and a leader that rejects the registration over duplicate uuids still answers lookups, so it stays the lookup target through the retry wait instead of leaving the previous master in place. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * volume: publish the master only once it is known leader (rust) * volume: ask the masters for the leader after losing the heartbeat (rust) * volume: read the leader field first on each heartbeat reply (rust) * volume: drop the seed fallback from ec shard lookups (rust) cached_lookup_ec_shard_locations fell back to the static first seed when no leader was published, so a mid-election EC lookup could land on a follower and read its stale shard map. Error out like Go's masterAddress() returning "" instead, via a current_master() getter matching getCurrentMaster. * volume: send volume lookups to the confirmed leader, not the seed (rust) Every HTTP lookup path — proxy/redirect targets, replicated writes and deletes, batch chunk deletes, remote chunk reads — dialed the static first seed, so after a failover they kept asking a follower whose vid map can be stale. Go routes all of these through GetMaster; switch the lookup calls and their leader-known gates to current_master(). * volume: mark volumes readonly on the leader, retrying the named one (rust) notify_master_volume_readonly fell back to the seed when no leader was published, which the peer answers with "Not current leader". It now uses only the confirmed leader, skips the call in masterless mode, and on NotLeader asks the seed masters who leads and retries there — the same path Go's notifyMasterVolumeReadonly takes. The peer leader query is shared with refresh_leader_from_peers as lookup_leader_from_peers. * volume: ask the confirmed leader for copy prealloc options (rust) VolumeCopy's preallocate query went to the static seed; Go issues it through GetMaster. Route it through current_master() too — when no leader is known the lookup fails fast and the copy proceeds without preallocating, as before. * volume: keep the announced master port forms, not just http (rust) publish_current_master stripped to http form, so a leader configured with an explicit gRPC port (host:9333.19999) was redialed on the default offset. Store the announced host:port[.grpcPort] form like Go's currentMaster; callers pick to_http_address / to_grpc_address / parse_grpc_address per use, and Ping admission compares on the http form. * volume: fail replicated writes and deletes when no leader is known (rust) Gating replication on a known leader meant a write could be acknowledged on fewer copies than required, and a delete could leave the data readable on the other replicas. Go fails the request when the replica-location lookup cannot run; drop the gate so the empty-leader lookup errors the same way. --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: Chris Lu <chrislusf@users.noreply.github.com>
440 lines
16 KiB
Go
440 lines
16 KiB
Go
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<<duplicateRetryCount) * 2 * time.Second // exponential backoff: 2s, 4s, 8s
|
|
glog.Errorf("Master reported duplicate volume directories: %v (retry %d/%d)", duplicateDir, duplicateRetryCount+1, maxRetries)
|
|
glog.Errorf("This might be due to a race condition during reconnection. Waiting %v before retrying...", retryDelay)
|
|
|
|
// Return error to trigger retry with increased count
|
|
doneChan <- fmt.Errorf("duplicate UUIDs detected, retrying connection (attempt %d/%d)", duplicateRetryCount+1, maxRetries)
|
|
return
|
|
} else {
|
|
// After max retries, this is likely a real duplicate
|
|
glog.Errorf("Shut down Volume Server due to persistent duplicate volume directories after %d retries: %v", maxRetries, duplicateDir)
|
|
glog.Errorf("Please check if another volume server is using the same directory")
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
vs.store.UpdateMaxVolumeIdSeen(needle.VolumeId(in.MaxVolumeId))
|
|
vs.store.UpdateLastSeenTopologyId(in.TopologyId)
|
|
volumeOptsChanged := false
|
|
if vs.store.GetPreallocate() != in.GetPreallocate() {
|
|
vs.store.SetPreallocate(in.GetPreallocate())
|
|
volumeOptsChanged = true
|
|
}
|
|
if in.GetVolumeSizeLimit() != 0 && vs.store.GetVolumeSizeLimit() != in.GetVolumeSizeLimit() {
|
|
vs.store.SetVolumeSizeLimit(in.GetVolumeSizeLimit())
|
|
volumeOptsChanged = true
|
|
}
|
|
if volumeOptsChanged {
|
|
if vs.store.MaybeAdjustVolumeMax() {
|
|
select {
|
|
case resendChan <- struct{}{}:
|
|
default:
|
|
}
|
|
}
|
|
}
|
|
if in.GetVolumeDigestSupported() {
|
|
vs.store.AcceptVolumeChanges()
|
|
}
|
|
if in.GetResendFullVolumeList() {
|
|
glog.V(0).Infof("master %s asked for the full volume list", masterAddress)
|
|
vs.store.RequestFullVolumeList()
|
|
}
|
|
}
|
|
}()
|
|
|
|
// This master may know nothing about this server, and has not yet said
|
|
// whether it understands digests, so start from the whole list.
|
|
vs.store.ResetVolumeReporting()
|
|
|
|
if err = stream.Send(vs.store.CollectHeartbeat()); err != nil {
|
|
glog.V(0).Infof("Volume Server Failed to talk with master %s: %v", masterAddress, err)
|
|
return "", err
|
|
}
|
|
|
|
if err = stream.Send(vs.store.CollectErasureCodingHeartbeat()); err != nil {
|
|
glog.V(0).Infof("Volume Server Failed to talk with master %s: %v", masterAddress, err)
|
|
return "", err
|
|
}
|
|
|
|
volumeTickChan := time.NewTicker(sleepInterval)
|
|
defer volumeTickChan.Stop()
|
|
ecShardTickChan := time.NewTicker(17 * sleepInterval)
|
|
defer ecShardTickChan.Stop()
|
|
dataCenter := vs.store.GetDataCenter()
|
|
rack := vs.store.GetRack()
|
|
ip := vs.store.Ip
|
|
port := uint32(vs.store.Port)
|
|
for {
|
|
select {
|
|
case stateMessage := <-vs.store.StateUpdateChan:
|
|
stateBeat := &master_pb.Heartbeat{
|
|
Ip: ip,
|
|
Port: port,
|
|
DataCenter: dataCenter,
|
|
Rack: rack,
|
|
State: stateMessage,
|
|
}
|
|
glog.V(0).Infof("volume server %s:%d updates state to %v", vs.store.Ip, vs.store.Port, stateMessage)
|
|
if err = stream.Send(stateBeat); err != nil {
|
|
glog.V(0).Infof("Volume Server Failed to update state to master %s: %v", masterAddress, err)
|
|
return "", err
|
|
}
|
|
case first := <-vs.store.NewVolumesChan:
|
|
volumes := util.DrainChannel(vs.store.NewVolumesChan, first)
|
|
deltaBeat := &master_pb.Heartbeat{
|
|
Ip: ip,
|
|
Port: port,
|
|
DataCenter: dataCenter,
|
|
Rack: rack,
|
|
NewVolumes: volumes,
|
|
}
|
|
for _, v := range volumes {
|
|
glog.V(0).Infof("volume server %s:%d adds volume %d", vs.store.Ip, vs.store.Port, v.Id)
|
|
}
|
|
if err = stream.Send(deltaBeat); err != nil {
|
|
glog.V(0).Infof("Volume Server Failed to update to master %s: %v", masterAddress, err)
|
|
return "", err
|
|
}
|
|
case first := <-vs.store.NewEcShardsChan:
|
|
shards := util.DrainChannel(vs.store.NewEcShardsChan, first)
|
|
deltaBeat := &master_pb.Heartbeat{
|
|
Ip: ip,
|
|
Port: port,
|
|
DataCenter: dataCenter,
|
|
Rack: rack,
|
|
NewEcShards: shards,
|
|
}
|
|
for _, s := range shards {
|
|
si := erasure_coding.ShardsInfoFromVolumeEcShardInformationMessage(s)
|
|
glog.V(0).Infof("volume server %s:%d adds ec shards to %d [%s]", vs.store.Ip, vs.store.Port, s.Id, si.String())
|
|
}
|
|
if err = stream.Send(deltaBeat); err != nil {
|
|
glog.V(0).Infof("Volume Server Failed to update to master %s: %v", masterAddress, err)
|
|
return "", err
|
|
}
|
|
case first := <-vs.store.DeletedVolumesChan:
|
|
volumes := util.DrainChannel(vs.store.DeletedVolumesChan, first)
|
|
deltaBeat := &master_pb.Heartbeat{
|
|
Ip: ip,
|
|
Port: port,
|
|
DataCenter: dataCenter,
|
|
Rack: rack,
|
|
DeletedVolumes: volumes,
|
|
}
|
|
for _, v := range volumes {
|
|
glog.V(0).Infof("volume server %s:%d deletes volume %d", vs.store.Ip, vs.store.Port, v.Id)
|
|
}
|
|
if err = stream.Send(deltaBeat); err != nil {
|
|
glog.V(0).Infof("Volume Server Failed to update to master %s: %v", masterAddress, err)
|
|
return "", err
|
|
}
|
|
case first := <-vs.store.DeletedEcShardsChan:
|
|
shards := util.DrainChannel(vs.store.DeletedEcShardsChan, first)
|
|
deltaBeat := &master_pb.Heartbeat{
|
|
Ip: ip,
|
|
Port: port,
|
|
DataCenter: dataCenter,
|
|
Rack: rack,
|
|
DeletedEcShards: shards,
|
|
}
|
|
for _, s := range shards {
|
|
glog.V(0).Infof("volume server %s:%d deletes ec shard %d:%s", vs.store.Ip, vs.store.Port, s.Id,
|
|
erasure_coding.ShardsInfoFromVolumeEcShardInformationMessage(s).String())
|
|
}
|
|
if err = stream.Send(deltaBeat); err != nil {
|
|
glog.V(0).Infof("Volume Server Failed to update to master %s: %v", masterAddress, err)
|
|
return "", err
|
|
}
|
|
case <-resendChan:
|
|
if err = stream.Send(vs.store.CollectHeartbeat()); err != nil {
|
|
glog.V(0).Infof("Volume Server Failed to talk with master %s: %v", masterAddress, err)
|
|
return "", err
|
|
}
|
|
case <-volumeTickChan.C:
|
|
glog.V(4).Infof("volume server %s:%d heartbeat", vs.store.Ip, vs.store.Port)
|
|
vs.store.MaybeAdjustVolumeMax()
|
|
if err = stream.Send(vs.store.CollectHeartbeat()); err != nil {
|
|
glog.V(0).Infof("Volume Server Failed to talk with master %s: %v", masterAddress, err)
|
|
return "", err
|
|
}
|
|
case <-ecShardTickChan.C:
|
|
glog.V(4).Infof("volume server %s:%d ec heartbeat", vs.store.Ip, vs.store.Port)
|
|
if err = stream.Send(vs.store.CollectErasureCodingHeartbeat()); err != nil {
|
|
glog.V(0).Infof("Volume Server Failed to talk with master %s: %v", masterAddress, err)
|
|
return "", err
|
|
}
|
|
case err = <-doneChan:
|
|
return
|
|
case <-vs.stopChan:
|
|
var volumeMessages []*master_pb.VolumeInformationMessage
|
|
emptyBeat := &master_pb.Heartbeat{
|
|
Ip: ip,
|
|
Port: port,
|
|
PublicUrl: vs.store.PublicUrl,
|
|
MaxFileKey: uint64(0),
|
|
DataCenter: dataCenter,
|
|
Rack: rack,
|
|
Volumes: volumeMessages,
|
|
HasNoVolumes: len(volumeMessages) == 0,
|
|
}
|
|
glog.V(1).Infof("volume server %s:%d stops and deletes all volumes", vs.store.Ip, vs.store.Port)
|
|
if err = stream.Send(emptyBeat); err != nil {
|
|
glog.V(0).Infof("Volume Server Failed to update to master %s: %v", masterAddress, err)
|
|
return "", err
|
|
}
|
|
return
|
|
}
|
|
}
|
|
}
|