github.com/resin-io/docker@v1.13.1/daemon/discovery.go (about) 1 package daemon 2 3 import ( 4 "errors" 5 "fmt" 6 "reflect" 7 "strconv" 8 "time" 9 10 "github.com/Sirupsen/logrus" 11 "github.com/docker/docker/pkg/discovery" 12 13 // Register the libkv backends for discovery. 14 _ "github.com/docker/docker/pkg/discovery/kv" 15 ) 16 17 const ( 18 // defaultDiscoveryHeartbeat is the default value for discovery heartbeat interval. 19 defaultDiscoveryHeartbeat = 20 * time.Second 20 // defaultDiscoveryTTLFactor is the default TTL factor for discovery 21 defaultDiscoveryTTLFactor = 3 22 ) 23 24 var errDiscoveryDisabled = errors.New("discovery is disabled") 25 26 type discoveryReloader interface { 27 discovery.Watcher 28 Stop() 29 Reload(backend, address string, clusterOpts map[string]string) error 30 ReadyCh() <-chan struct{} 31 } 32 33 type daemonDiscoveryReloader struct { 34 backend discovery.Backend 35 ticker *time.Ticker 36 term chan bool 37 readyCh chan struct{} 38 } 39 40 func (d *daemonDiscoveryReloader) Watch(stopCh <-chan struct{}) (<-chan discovery.Entries, <-chan error) { 41 return d.backend.Watch(stopCh) 42 } 43 44 func (d *daemonDiscoveryReloader) ReadyCh() <-chan struct{} { 45 return d.readyCh 46 } 47 48 func discoveryOpts(clusterOpts map[string]string) (time.Duration, time.Duration, error) { 49 var ( 50 heartbeat = defaultDiscoveryHeartbeat 51 ttl = defaultDiscoveryTTLFactor * defaultDiscoveryHeartbeat 52 ) 53 54 if hb, ok := clusterOpts["discovery.heartbeat"]; ok { 55 h, err := strconv.Atoi(hb) 56 if err != nil { 57 return time.Duration(0), time.Duration(0), err 58 } 59 60 if h <= 0 { 61 return time.Duration(0), time.Duration(0), 62 fmt.Errorf("discovery.heartbeat must be positive") 63 } 64 65 heartbeat = time.Duration(h) * time.Second 66 ttl = defaultDiscoveryTTLFactor * heartbeat 67 } 68 69 if tstr, ok := clusterOpts["discovery.ttl"]; ok { 70 t, err := strconv.Atoi(tstr) 71 if err != nil { 72 return time.Duration(0), time.Duration(0), err 73 } 74 75 if t <= 0 { 76 return time.Duration(0), time.Duration(0), 77 fmt.Errorf("discovery.ttl must be positive") 78 } 79 80 ttl = time.Duration(t) * time.Second 81 82 if _, ok := clusterOpts["discovery.heartbeat"]; !ok { 83 h := int(t / defaultDiscoveryTTLFactor) 84 heartbeat = time.Duration(h) * time.Second 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 // initDiscovery 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 initDiscovery(backendAddress, advertiseAddress string, clusterOpts map[string]string) (discoveryReloader, 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 } 125 126 for { 127 select { 128 case <-d.ticker.C: 129 if err := d.backend.Register(address); err != nil { 130 logrus.Warnf("Registering as %q in discovery failed: %v", address, err) 131 } else { 132 if !ready { 133 close(d.readyCh) 134 ready = true 135 } 136 } 137 case <-d.term: 138 return 139 } 140 } 141 } 142 143 // initHeartbeat is used to do the first heartbeat. It uses a tight loop until 144 // either the timeout period is reached or the heartbeat is successful and returns. 145 func (d *daemonDiscoveryReloader) initHeartbeat(address string) error { 146 // Setup a short ticker until the first heartbeat has succeeded 147 t := time.NewTicker(500 * time.Millisecond) 148 defer t.Stop() 149 // timeout makes sure that after a period of time we stop being so aggressive trying to reach the discovery service 150 timeout := time.After(60 * time.Second) 151 152 for { 153 select { 154 case <-timeout: 155 return errors.New("timeout waiting for initial discovery") 156 case <-d.term: 157 return errors.New("terminated") 158 case <-t.C: 159 if err := d.backend.Register(address); err == nil { 160 return nil 161 } 162 } 163 } 164 } 165 166 // Reload makes the watcher to stop advertising and reconfigures it to advertise in a new address. 167 func (d *daemonDiscoveryReloader) Reload(backendAddress, advertiseAddress string, clusterOpts map[string]string) error { 168 d.Stop() 169 170 heartbeat, backend, err := parseDiscoveryOptions(backendAddress, clusterOpts) 171 if err != nil { 172 return err 173 } 174 175 d.backend = backend 176 d.ticker = time.NewTicker(heartbeat) 177 d.readyCh = make(chan struct{}) 178 179 go d.advertiseHeartbeat(advertiseAddress) 180 return nil 181 } 182 183 // Stop terminates the discovery advertising. 184 func (d *daemonDiscoveryReloader) Stop() { 185 d.ticker.Stop() 186 d.term <- true 187 } 188 189 func parseDiscoveryOptions(backendAddress string, clusterOpts map[string]string) (time.Duration, discovery.Backend, error) { 190 heartbeat, ttl, err := discoveryOpts(clusterOpts) 191 if err != nil { 192 return 0, nil, err 193 } 194 195 backend, err := discovery.New(backendAddress, heartbeat, ttl, clusterOpts) 196 if err != nil { 197 return 0, nil, err 198 } 199 return heartbeat, backend, nil 200 } 201 202 // modifiedDiscoverySettings returns whether the discovery configuration has been modified or not. 203 func modifiedDiscoverySettings(config *Config, backendType, advertise string, clusterOpts map[string]string) bool { 204 if config.ClusterStore != backendType || config.ClusterAdvertise != advertise { 205 return true 206 } 207 208 if (config.ClusterOpts == nil && clusterOpts == nil) || 209 (config.ClusterOpts == nil && len(clusterOpts) == 0) || 210 (len(config.ClusterOpts) == 0 && clusterOpts == nil) { 211 return false 212 } 213 214 return !reflect.DeepEqual(config.ClusterOpts, clusterOpts) 215 }