github.com/LazyboyChen7/engine@v17.12.1-ce-rc2+incompatible/daemon/discovery/discovery.go (about) 1 package discovery 2 3 import ( 4 "errors" 5 "fmt" 6 "strconv" 7 "time" 8 9 "github.com/docker/docker/pkg/discovery" 10 "github.com/sirupsen/logrus" 11 12 // Register the libkv backends for discovery. 13 _ "github.com/docker/docker/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 // timeout makes sure that after a period of time we stop being so aggressive trying to reach the discovery service 152 timeout := time.After(60 * time.Second) 153 154 for { 155 select { 156 case <-timeout: 157 return errors.New("timeout waiting for initial discovery") 158 case <-d.term: 159 return errors.New("terminated") 160 case <-t.C: 161 if err := d.backend.Register(address); err == nil { 162 return nil 163 } 164 } 165 } 166 } 167 168 // Reload makes the watcher to stop advertising and reconfigures it to advertise in a new address. 169 func (d *daemonDiscoveryReloader) Reload(backendAddress, advertiseAddress string, clusterOpts map[string]string) error { 170 d.Stop() 171 172 heartbeat, backend, err := parseDiscoveryOptions(backendAddress, clusterOpts) 173 if err != nil { 174 return err 175 } 176 177 d.backend = backend 178 d.ticker = time.NewTicker(heartbeat) 179 d.readyCh = make(chan struct{}) 180 181 go d.advertiseHeartbeat(advertiseAddress) 182 return nil 183 } 184 185 // Stop terminates the discovery advertising. 186 func (d *daemonDiscoveryReloader) Stop() { 187 d.ticker.Stop() 188 d.term <- true 189 } 190 191 func parseDiscoveryOptions(backendAddress string, clusterOpts map[string]string) (time.Duration, discovery.Backend, error) { 192 heartbeat, ttl, err := discoveryOpts(clusterOpts) 193 if err != nil { 194 return 0, nil, err 195 } 196 197 backend, err := discovery.New(backendAddress, heartbeat, ttl, clusterOpts) 198 if err != nil { 199 return 0, nil, err 200 } 201 return heartbeat, backend, nil 202 }