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 }