github.com/demonoid81/moby@v0.0.0-20200517203328-62dd8e17c460/daemon/discovery/discovery.go (about)

     1  package discovery // import "github.com/demonoid81/moby/daemon/discovery"
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"strconv"
     7  	"time"
     8  
     9  	"github.com/demonoid81/moby/pkg/discovery"
    10  	"github.com/sirupsen/logrus"
    11  
    12  	// Register the libkv backends for discovery.
    13  	_ "github.com/demonoid81/moby/pkg/discovery/kv"
    14  )
    15  
    16  const (
    17  	// defaultDiscoveryHeartbeat is the default value for discovery heartbeat interval.
    18  	defaultDiscoveryHeartbeat = 20 * time.Second
    19  	// defaultDiscoveryTTLFactor is the default TTL factor for discovery
    20  	defaultDiscoveryTTLFactor = 3
    21  )
    22  
    23  // ErrDiscoveryDisabled is an error returned if the discovery is disabled
    24  var ErrDiscoveryDisabled = errors.New("discovery is disabled")
    25  
    26  // Reloader is the discovery reloader of the daemon
    27  type Reloader interface {
    28  	discovery.Watcher
    29  	Stop()
    30  	Reload(backend, address string, clusterOpts map[string]string) error
    31  	ReadyCh() <-chan struct{}
    32  }
    33  
    34  type daemonDiscoveryReloader struct {
    35  	backend discovery.Backend
    36  	ticker  *time.Ticker
    37  	term    chan bool
    38  	readyCh chan struct{}
    39  }
    40  
    41  func (d *daemonDiscoveryReloader) Watch(stopCh <-chan struct{}) (<-chan discovery.Entries, <-chan error) {
    42  	return d.backend.Watch(stopCh)
    43  }
    44  
    45  func (d *daemonDiscoveryReloader) ReadyCh() <-chan struct{} {
    46  	return d.readyCh
    47  }
    48  
    49  func discoveryOpts(clusterOpts map[string]string) (time.Duration, time.Duration, error) {
    50  	var (
    51  		heartbeat = defaultDiscoveryHeartbeat
    52  		ttl       = defaultDiscoveryTTLFactor * defaultDiscoveryHeartbeat
    53  	)
    54  
    55  	if hb, ok := clusterOpts["discovery.heartbeat"]; ok {
    56  		h, err := strconv.Atoi(hb)
    57  		if err != nil {
    58  			return time.Duration(0), time.Duration(0), err
    59  		}
    60  
    61  		if h <= 0 {
    62  			return time.Duration(0), time.Duration(0),
    63  				fmt.Errorf("discovery.heartbeat must be positive")
    64  		}
    65  
    66  		heartbeat = time.Duration(h) * time.Second
    67  		ttl = defaultDiscoveryTTLFactor * heartbeat
    68  	}
    69  
    70  	if tstr, ok := clusterOpts["discovery.ttl"]; ok {
    71  		t, err := strconv.Atoi(tstr)
    72  		if err != nil {
    73  			return time.Duration(0), time.Duration(0), err
    74  		}
    75  
    76  		if t <= 0 {
    77  			return time.Duration(0), time.Duration(0),
    78  				fmt.Errorf("discovery.ttl must be positive")
    79  		}
    80  
    81  		ttl = time.Duration(t) * time.Second
    82  
    83  		if _, ok := clusterOpts["discovery.heartbeat"]; !ok {
    84  			heartbeat = time.Duration(t) * time.Second / time.Duration(defaultDiscoveryTTLFactor)
    85  		}
    86  
    87  		if ttl <= heartbeat {
    88  			return time.Duration(0), time.Duration(0),
    89  				fmt.Errorf("discovery.ttl timer must be greater than discovery.heartbeat")
    90  		}
    91  	}
    92  
    93  	return heartbeat, ttl, nil
    94  }
    95  
    96  // Init initializes the nodes discovery subsystem by connecting to the specified backend
    97  // and starts a registration loop to advertise the current node under the specified address.
    98  func Init(backendAddress, advertiseAddress string, clusterOpts map[string]string) (Reloader, error) {
    99  	heartbeat, backend, err := parseDiscoveryOptions(backendAddress, clusterOpts)
   100  	if err != nil {
   101  		return nil, err
   102  	}
   103  
   104  	reloader := &daemonDiscoveryReloader{
   105  		backend: backend,
   106  		ticker:  time.NewTicker(heartbeat),
   107  		term:    make(chan bool),
   108  		readyCh: make(chan struct{}),
   109  	}
   110  	// We call Register() on the discovery backend in a loop for the whole lifetime of the daemon,
   111  	// but we never actually Watch() for nodes appearing and disappearing for the moment.
   112  	go reloader.advertiseHeartbeat(advertiseAddress)
   113  	return reloader, nil
   114  }
   115  
   116  // advertiseHeartbeat registers the current node against the discovery backend using the specified
   117  // address. The function never returns, as registration against the backend comes with a TTL and
   118  // requires regular heartbeats.
   119  func (d *daemonDiscoveryReloader) advertiseHeartbeat(address string) {
   120  	var ready bool
   121  	if err := d.initHeartbeat(address); err == nil {
   122  		ready = true
   123  		close(d.readyCh)
   124  	} else {
   125  		logrus.WithError(err).Debug("First discovery heartbeat failed")
   126  	}
   127  
   128  	for {
   129  		select {
   130  		case <-d.ticker.C:
   131  			if err := d.backend.Register(address); err != nil {
   132  				logrus.Warnf("Registering as %q in discovery failed: %v", address, err)
   133  			} else {
   134  				if !ready {
   135  					close(d.readyCh)
   136  					ready = true
   137  				}
   138  			}
   139  		case <-d.term:
   140  			return
   141  		}
   142  	}
   143  }
   144  
   145  // initHeartbeat is used to do the first heartbeat. It uses a tight loop until
   146  // either the timeout period is reached or the heartbeat is successful and returns.
   147  func (d *daemonDiscoveryReloader) initHeartbeat(address string) error {
   148  	// Setup a short ticker until the first heartbeat has succeeded
   149  	t := time.NewTicker(500 * time.Millisecond)
   150  	defer t.Stop()
   151  
   152  	// timeout makes sure that after a period of time we stop being so aggressive trying to reach the discovery service
   153  	timeout := time.NewTimer(60 * time.Second)
   154  	defer timeout.Stop()
   155  
   156  	for {
   157  		select {
   158  		case <-timeout.C:
   159  			return errors.New("timeout waiting for initial discovery")
   160  		case <-d.term:
   161  			return errors.New("terminated")
   162  		case <-t.C:
   163  			if err := d.backend.Register(address); err == nil {
   164  				return nil
   165  			}
   166  		}
   167  	}
   168  }
   169  
   170  // Reload makes the watcher to stop advertising and reconfigures it to advertise in a new address.
   171  func (d *daemonDiscoveryReloader) Reload(backendAddress, advertiseAddress string, clusterOpts map[string]string) error {
   172  	d.Stop()
   173  
   174  	heartbeat, backend, err := parseDiscoveryOptions(backendAddress, clusterOpts)
   175  	if err != nil {
   176  		return err
   177  	}
   178  
   179  	d.backend = backend
   180  	d.ticker = time.NewTicker(heartbeat)
   181  	d.readyCh = make(chan struct{})
   182  
   183  	go d.advertiseHeartbeat(advertiseAddress)
   184  	return nil
   185  }
   186  
   187  // Stop terminates the discovery advertising.
   188  func (d *daemonDiscoveryReloader) Stop() {
   189  	d.ticker.Stop()
   190  	d.term <- true
   191  }
   192  
   193  func parseDiscoveryOptions(backendAddress string, clusterOpts map[string]string) (time.Duration, discovery.Backend, error) {
   194  	heartbeat, ttl, err := discoveryOpts(clusterOpts)
   195  	if err != nil {
   196  		return 0, nil, err
   197  	}
   198  
   199  	backend, err := discovery.New(backendAddress, heartbeat, ttl, clusterOpts)
   200  	if err != nil {
   201  		return 0, nil, err
   202  	}
   203  	return heartbeat, backend, nil
   204  }