github.com/fabiokung/docker@v0.11.2-0.20170222101415-4534dcd49497/daemon/discovery/discovery.go (about) 1 package discovery 2 3 import ( 4 "errors" 5 "fmt" 6 "strconv" 7 "time" 8 9 "github.com/Sirupsen/logrus" 10 "github.com/docker/docker/pkg/discovery" 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 h := int(t / defaultDiscoveryTTLFactor) 85 heartbeat = time.Duration(h) * time.Second 86 } 87 88 if ttl <= heartbeat { 89 return time.Duration(0), time.Duration(0), 90 fmt.Errorf("discovery.ttl timer must be greater than discovery.heartbeat") 91 } 92 } 93 94 return heartbeat, ttl, nil 95 } 96 97 // Init initializes the nodes discovery subsystem by connecting to the specified backend 98 // and starts a registration loop to advertise the current node under the specified address. 99 func Init(backendAddress, advertiseAddress string, clusterOpts map[string]string) (Reloader, error) { 100 heartbeat, backend, err := parseDiscoveryOptions(backendAddress, clusterOpts) 101 if err != nil { 102 return nil, err 103 } 104 105 reloader := &daemonDiscoveryReloader{ 106 backend: backend, 107 ticker: time.NewTicker(heartbeat), 108 term: make(chan bool), 109 readyCh: make(chan struct{}), 110 } 111 // We call Register() on the discovery backend in a loop for the whole lifetime of the daemon, 112 // but we never actually Watch() for nodes appearing and disappearing for the moment. 113 go reloader.advertiseHeartbeat(advertiseAddress) 114 return reloader, nil 115 } 116 117 // advertiseHeartbeat registers the current node against the discovery backend using the specified 118 // address. The function never returns, as registration against the backend comes with a TTL and 119 // requires regular heartbeats. 120 func (d *daemonDiscoveryReloader) advertiseHeartbeat(address string) { 121 var ready bool 122 if err := d.initHeartbeat(address); err == nil { 123 ready = true 124 close(d.readyCh) 125 } 126 127 for { 128 select { 129 case <-d.ticker.C: 130 if err := d.backend.Register(address); err != nil { 131 logrus.Warnf("Registering as %q in discovery failed: %v", address, err) 132 } else { 133 if !ready { 134 close(d.readyCh) 135 ready = true 136 } 137 } 138 case <-d.term: 139 return 140 } 141 } 142 } 143 144 // initHeartbeat is used to do the first heartbeat. It uses a tight loop until 145 // either the timeout period is reached or the heartbeat is successful and returns. 146 func (d *daemonDiscoveryReloader) initHeartbeat(address string) error { 147 // Setup a short ticker until the first heartbeat has succeeded 148 t := time.NewTicker(500 * time.Millisecond) 149 defer t.Stop() 150 // timeout makes sure that after a period of time we stop being so aggressive trying to reach the discovery service 151 timeout := time.After(60 * time.Second) 152 153 for { 154 select { 155 case <-timeout: 156 return errors.New("timeout waiting for initial discovery") 157 case <-d.term: 158 return errors.New("terminated") 159 case <-t.C: 160 if err := d.backend.Register(address); err == nil { 161 return nil 162 } 163 } 164 } 165 } 166 167 // Reload makes the watcher to stop advertising and reconfigures it to advertise in a new address. 168 func (d *daemonDiscoveryReloader) Reload(backendAddress, advertiseAddress string, clusterOpts map[string]string) error { 169 d.Stop() 170 171 heartbeat, backend, err := parseDiscoveryOptions(backendAddress, clusterOpts) 172 if err != nil { 173 return err 174 } 175 176 d.backend = backend 177 d.ticker = time.NewTicker(heartbeat) 178 d.readyCh = make(chan struct{}) 179 180 go d.advertiseHeartbeat(advertiseAddress) 181 return nil 182 } 183 184 // Stop terminates the discovery advertising. 185 func (d *daemonDiscoveryReloader) Stop() { 186 d.ticker.Stop() 187 d.term <- true 188 } 189 190 func parseDiscoveryOptions(backendAddress string, clusterOpts map[string]string) (time.Duration, discovery.Backend, error) { 191 heartbeat, ttl, err := discoveryOpts(clusterOpts) 192 if err != nil { 193 return 0, nil, err 194 } 195 196 backend, err := discovery.New(backendAddress, heartbeat, ttl, clusterOpts) 197 if err != nil { 198 return 0, nil, err 199 } 200 return heartbeat, backend, nil 201 }