github.com/kaisenlinux/docker.io@v0.0.0-20230510090727-ea55db55fac7/swarmkit/manager/dispatcher/nodes.go (about)

     1  package dispatcher
     2  
     3  import (
     4  	"sync"
     5  	"time"
     6  
     7  	"github.com/docker/swarmkit/api"
     8  	"github.com/docker/swarmkit/identity"
     9  	"github.com/docker/swarmkit/manager/dispatcher/heartbeat"
    10  	"google.golang.org/grpc/codes"
    11  	"google.golang.org/grpc/status"
    12  )
    13  
    14  const rateLimitCount = 3
    15  
    16  type registeredNode struct {
    17  	SessionID  string
    18  	Heartbeat  *heartbeat.Heartbeat
    19  	Registered time.Time
    20  	Attempts   int
    21  	Node       *api.Node
    22  	Disconnect chan struct{} // signal to disconnect
    23  	mu         sync.Mutex
    24  }
    25  
    26  // checkSessionID determines if the SessionID has changed and returns the
    27  // appropriate GRPC error code.
    28  //
    29  // This may not belong here in the future.
    30  func (rn *registeredNode) checkSessionID(sessionID string) error {
    31  	rn.mu.Lock()
    32  	defer rn.mu.Unlock()
    33  
    34  	// Before each message send, we need to check the nodes sessionID hasn't
    35  	// changed. If it has, we will the stream and make the node
    36  	// re-register.
    37  	if sessionID == "" || rn.SessionID != sessionID {
    38  		return status.Errorf(codes.InvalidArgument, ErrSessionInvalid.Error())
    39  	}
    40  
    41  	return nil
    42  }
    43  
    44  type nodeStore struct {
    45  	periodChooser                *periodChooser
    46  	gracePeriodMultiplierNormal  time.Duration
    47  	gracePeriodMultiplierUnknown time.Duration
    48  	rateLimitPeriod              time.Duration
    49  	nodes                        map[string]*registeredNode
    50  	mu                           sync.RWMutex
    51  }
    52  
    53  func newNodeStore(hbPeriod, hbEpsilon time.Duration, graceMultiplier int, rateLimitPeriod time.Duration) *nodeStore {
    54  	return &nodeStore{
    55  		nodes:                        make(map[string]*registeredNode),
    56  		periodChooser:                newPeriodChooser(hbPeriod, hbEpsilon),
    57  		gracePeriodMultiplierNormal:  time.Duration(graceMultiplier),
    58  		gracePeriodMultiplierUnknown: time.Duration(graceMultiplier) * 2,
    59  		rateLimitPeriod:              rateLimitPeriod,
    60  	}
    61  }
    62  
    63  func (s *nodeStore) updatePeriod(hbPeriod, hbEpsilon time.Duration, gracePeriodMultiplier int) {
    64  	s.mu.Lock()
    65  	s.periodChooser = newPeriodChooser(hbPeriod, hbEpsilon)
    66  	s.gracePeriodMultiplierNormal = time.Duration(gracePeriodMultiplier)
    67  	s.gracePeriodMultiplierUnknown = s.gracePeriodMultiplierNormal * 2
    68  	s.mu.Unlock()
    69  }
    70  
    71  func (s *nodeStore) Len() int {
    72  	s.mu.Lock()
    73  	defer s.mu.Unlock()
    74  	return len(s.nodes)
    75  }
    76  
    77  func (s *nodeStore) AddUnknown(n *api.Node, expireFunc func()) error {
    78  	s.mu.Lock()
    79  	defer s.mu.Unlock()
    80  	rn := &registeredNode{
    81  		Node: n,
    82  	}
    83  	s.nodes[n.ID] = rn
    84  	rn.Heartbeat = heartbeat.New(s.periodChooser.Choose()*s.gracePeriodMultiplierUnknown, expireFunc)
    85  	return nil
    86  }
    87  
    88  // CheckRateLimit returns error if node with specified id is allowed to re-register
    89  // again.
    90  func (s *nodeStore) CheckRateLimit(id string) error {
    91  	s.mu.Lock()
    92  	defer s.mu.Unlock()
    93  	if existRn, ok := s.nodes[id]; ok {
    94  		if time.Since(existRn.Registered) > s.rateLimitPeriod {
    95  			existRn.Attempts = 0
    96  		}
    97  		existRn.Attempts++
    98  		if existRn.Attempts > rateLimitCount {
    99  			return status.Errorf(codes.Unavailable, "node %s exceeded rate limit count of registrations", id)
   100  		}
   101  		existRn.Registered = time.Now()
   102  	}
   103  	return nil
   104  }
   105  
   106  // Add adds new node and returns it, it replaces existing without notification.
   107  func (s *nodeStore) Add(n *api.Node, expireFunc func()) *registeredNode {
   108  	s.mu.Lock()
   109  	defer s.mu.Unlock()
   110  	var attempts int
   111  	var registered time.Time
   112  	if existRn, ok := s.nodes[n.ID]; ok {
   113  		attempts = existRn.Attempts
   114  		registered = existRn.Registered
   115  		existRn.Heartbeat.Stop()
   116  		delete(s.nodes, n.ID)
   117  	}
   118  	if registered.IsZero() {
   119  		registered = time.Now()
   120  	}
   121  	rn := &registeredNode{
   122  		SessionID:  identity.NewID(), // session ID is local to the dispatcher.
   123  		Node:       n,
   124  		Registered: registered,
   125  		Attempts:   attempts,
   126  		Disconnect: make(chan struct{}),
   127  	}
   128  	s.nodes[n.ID] = rn
   129  	rn.Heartbeat = heartbeat.New(s.periodChooser.Choose()*s.gracePeriodMultiplierNormal, expireFunc)
   130  	return rn
   131  }
   132  
   133  func (s *nodeStore) Get(id string) (*registeredNode, error) {
   134  	s.mu.RLock()
   135  	rn, ok := s.nodes[id]
   136  	s.mu.RUnlock()
   137  	if !ok {
   138  		return nil, status.Errorf(codes.NotFound, ErrNodeNotRegistered.Error())
   139  	}
   140  	return rn, nil
   141  }
   142  
   143  func (s *nodeStore) GetWithSession(id, sid string) (*registeredNode, error) {
   144  	s.mu.RLock()
   145  	rn, ok := s.nodes[id]
   146  	s.mu.RUnlock()
   147  	if !ok {
   148  		return nil, status.Errorf(codes.NotFound, ErrNodeNotRegistered.Error())
   149  	}
   150  	return rn, rn.checkSessionID(sid)
   151  }
   152  
   153  func (s *nodeStore) Heartbeat(id, sid string) (time.Duration, error) {
   154  	rn, err := s.GetWithSession(id, sid)
   155  	if err != nil {
   156  		return 0, err
   157  	}
   158  	period := s.periodChooser.Choose() // base period for node
   159  	grace := period * s.gracePeriodMultiplierNormal
   160  	rn.mu.Lock()
   161  	rn.Heartbeat.Update(grace)
   162  	rn.Heartbeat.Beat()
   163  	rn.mu.Unlock()
   164  	return period, nil
   165  }
   166  
   167  func (s *nodeStore) Delete(id string) *registeredNode {
   168  	s.mu.Lock()
   169  	var node *registeredNode
   170  	if rn, ok := s.nodes[id]; ok {
   171  		delete(s.nodes, id)
   172  		rn.Heartbeat.Stop()
   173  		node = rn
   174  	}
   175  	s.mu.Unlock()
   176  	return node
   177  }
   178  
   179  func (s *nodeStore) Disconnect(id string) {
   180  	s.mu.Lock()
   181  	if rn, ok := s.nodes[id]; ok {
   182  		close(rn.Disconnect)
   183  		rn.Heartbeat.Stop()
   184  	}
   185  	s.mu.Unlock()
   186  }
   187  
   188  // Clean removes all nodes and stops their heartbeats.
   189  // It's equivalent to invalidate all sessions.
   190  func (s *nodeStore) Clean() {
   191  	s.mu.Lock()
   192  	for _, rn := range s.nodes {
   193  		rn.Heartbeat.Stop()
   194  	}
   195  	s.nodes = make(map[string]*registeredNode)
   196  	s.mu.Unlock()
   197  }