discovery.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. // Copyright 2015 The etcd Authors
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. // Package discovery provides an implementation of the cluster discovery that
  15. // is used by etcd.
  16. package discovery
  17. import (
  18. "errors"
  19. "fmt"
  20. "math"
  21. "net/http"
  22. "net/url"
  23. "path"
  24. "sort"
  25. "strconv"
  26. "strings"
  27. "time"
  28. "github.com/coreos/etcd/client"
  29. "github.com/coreos/etcd/pkg/transport"
  30. "github.com/coreos/etcd/pkg/types"
  31. "github.com/coreos/pkg/capnslog"
  32. "github.com/jonboulle/clockwork"
  33. "golang.org/x/net/context"
  34. )
  35. var (
  36. plog = capnslog.NewPackageLogger("github.com/coreos/etcd", "discovery")
  37. ErrInvalidURL = errors.New("discovery: invalid URL")
  38. ErrBadSizeKey = errors.New("discovery: size key is bad")
  39. ErrSizeNotFound = errors.New("discovery: size key not found")
  40. ErrTokenNotFound = errors.New("discovery: token not found")
  41. ErrDuplicateID = errors.New("discovery: found duplicate id")
  42. ErrDuplicateName = errors.New("discovery: found duplicate name")
  43. ErrFullCluster = errors.New("discovery: cluster is full")
  44. ErrTooManyRetries = errors.New("discovery: too many retries")
  45. ErrBadDiscoveryEndpoint = errors.New("discovery: bad discovery endpoint")
  46. )
  47. var (
  48. // Number of retries discovery will attempt before giving up and erroring out.
  49. nRetries = uint(math.MaxUint32)
  50. maxExpoentialRetries = uint(8)
  51. )
  52. // JoinCluster will connect to the discovery service at the given url, and
  53. // register the server represented by the given id and config to the cluster
  54. func JoinCluster(durl, dproxyurl string, id types.ID, config string) (string, error) {
  55. d, err := newDiscovery(durl, dproxyurl, id)
  56. if err != nil {
  57. return "", err
  58. }
  59. return d.joinCluster(config)
  60. }
  61. // GetCluster will connect to the discovery service at the given url and
  62. // retrieve a string describing the cluster
  63. func GetCluster(durl, dproxyurl string) (string, error) {
  64. d, err := newDiscovery(durl, dproxyurl, 0)
  65. if err != nil {
  66. return "", err
  67. }
  68. return d.getCluster()
  69. }
  70. type discovery struct {
  71. cluster string
  72. id types.ID
  73. c client.KeysAPI
  74. retries uint
  75. url *url.URL
  76. clock clockwork.Clock
  77. }
  78. // newProxyFunc builds a proxy function from the given string, which should
  79. // represent a URL that can be used as a proxy. It performs basic
  80. // sanitization of the URL and returns any error encountered.
  81. func newProxyFunc(proxy string) (func(*http.Request) (*url.URL, error), error) {
  82. if proxy == "" {
  83. return nil, nil
  84. }
  85. // Do a small amount of URL sanitization to help the user
  86. // Derived from net/http.ProxyFromEnvironment
  87. proxyURL, err := url.Parse(proxy)
  88. if err != nil || !strings.HasPrefix(proxyURL.Scheme, "http") {
  89. // proxy was bogus. Try prepending "http://" to it and
  90. // see if that parses correctly. If not, we ignore the
  91. // error and complain about the original one
  92. var err2 error
  93. proxyURL, err2 = url.Parse("http://" + proxy)
  94. if err2 == nil {
  95. err = nil
  96. }
  97. }
  98. if err != nil {
  99. return nil, fmt.Errorf("invalid proxy address %q: %v", proxy, err)
  100. }
  101. plog.Infof("using proxy %q", proxyURL.String())
  102. return http.ProxyURL(proxyURL), nil
  103. }
  104. func newDiscovery(durl, dproxyurl string, id types.ID) (*discovery, error) {
  105. u, err := url.Parse(durl)
  106. if err != nil {
  107. return nil, err
  108. }
  109. token := u.Path
  110. u.Path = ""
  111. pf, err := newProxyFunc(dproxyurl)
  112. if err != nil {
  113. return nil, err
  114. }
  115. // TODO: add ResponseHeaderTimeout back when watch on discovery service writes header early
  116. tr, err := transport.NewTransport(transport.TLSInfo{}, 30*time.Second)
  117. if err != nil {
  118. return nil, err
  119. }
  120. tr.Proxy = pf
  121. cfg := client.Config{
  122. Transport: tr,
  123. Endpoints: []string{u.String()},
  124. }
  125. c, err := client.New(cfg)
  126. if err != nil {
  127. return nil, err
  128. }
  129. dc := client.NewKeysAPIWithPrefix(c, "")
  130. return &discovery{
  131. cluster: token,
  132. c: dc,
  133. id: id,
  134. url: u,
  135. clock: clockwork.NewRealClock(),
  136. }, nil
  137. }
  138. func (d *discovery) joinCluster(config string) (string, error) {
  139. // fast path: if the cluster is full, return the error
  140. // do not need to register to the cluster in this case.
  141. if _, _, _, err := d.checkCluster(); err != nil {
  142. return "", err
  143. }
  144. if err := d.createSelf(config); err != nil {
  145. // Fails, even on a timeout, if createSelf times out.
  146. // TODO(barakmich): Retrying the same node might want to succeed here
  147. // (ie, createSelf should be idempotent for discovery).
  148. return "", err
  149. }
  150. nodes, size, index, err := d.checkCluster()
  151. if err != nil {
  152. return "", err
  153. }
  154. all, err := d.waitNodes(nodes, size, index)
  155. if err != nil {
  156. return "", err
  157. }
  158. return nodesToCluster(all, size)
  159. }
  160. func (d *discovery) getCluster() (string, error) {
  161. nodes, size, index, err := d.checkCluster()
  162. if err != nil {
  163. if err == ErrFullCluster {
  164. return nodesToCluster(nodes, size)
  165. }
  166. return "", err
  167. }
  168. all, err := d.waitNodes(nodes, size, index)
  169. if err != nil {
  170. return "", err
  171. }
  172. return nodesToCluster(all, size)
  173. }
  174. func (d *discovery) createSelf(contents string) error {
  175. ctx, cancel := context.WithTimeout(context.Background(), client.DefaultRequestTimeout)
  176. resp, err := d.c.Create(ctx, d.selfKey(), contents)
  177. cancel()
  178. if err != nil {
  179. if eerr, ok := err.(client.Error); ok && eerr.Code == client.ErrorCodeNodeExist {
  180. return ErrDuplicateID
  181. }
  182. return err
  183. }
  184. // ensure self appears on the server we connected to
  185. w := d.c.Watcher(d.selfKey(), &client.WatcherOptions{AfterIndex: resp.Node.CreatedIndex - 1})
  186. _, err = w.Next(context.Background())
  187. return err
  188. }
  189. func (d *discovery) checkCluster() ([]*client.Node, int, uint64, error) {
  190. configKey := path.Join("/", d.cluster, "_config")
  191. ctx, cancel := context.WithTimeout(context.Background(), client.DefaultRequestTimeout)
  192. // find cluster size
  193. resp, err := d.c.Get(ctx, path.Join(configKey, "size"), nil)
  194. cancel()
  195. if err != nil {
  196. if eerr, ok := err.(*client.Error); ok && eerr.Code == client.ErrorCodeKeyNotFound {
  197. return nil, 0, 0, ErrSizeNotFound
  198. }
  199. if err == client.ErrInvalidJSON {
  200. return nil, 0, 0, ErrBadDiscoveryEndpoint
  201. }
  202. if ce, ok := err.(*client.ClusterError); ok {
  203. plog.Error(ce.Detail())
  204. return d.checkClusterRetry()
  205. }
  206. return nil, 0, 0, err
  207. }
  208. size, err := strconv.Atoi(resp.Node.Value)
  209. if err != nil {
  210. return nil, 0, 0, ErrBadSizeKey
  211. }
  212. ctx, cancel = context.WithTimeout(context.Background(), client.DefaultRequestTimeout)
  213. resp, err = d.c.Get(ctx, d.cluster, nil)
  214. cancel()
  215. if err != nil {
  216. if ce, ok := err.(*client.ClusterError); ok {
  217. plog.Error(ce.Detail())
  218. return d.checkClusterRetry()
  219. }
  220. return nil, 0, 0, err
  221. }
  222. var nodes []*client.Node
  223. // append non-config keys to nodes
  224. for _, n := range resp.Node.Nodes {
  225. if !(path.Base(n.Key) == path.Base(configKey)) {
  226. nodes = append(nodes, n)
  227. }
  228. }
  229. snodes := sortableNodes{nodes}
  230. sort.Sort(snodes)
  231. // find self position
  232. for i := range nodes {
  233. if path.Base(nodes[i].Key) == path.Base(d.selfKey()) {
  234. break
  235. }
  236. if i >= size-1 {
  237. return nodes[:size], size, resp.Index, ErrFullCluster
  238. }
  239. }
  240. return nodes, size, resp.Index, nil
  241. }
  242. func (d *discovery) logAndBackoffForRetry(step string) {
  243. d.retries++
  244. // logAndBackoffForRetry stops exponential backoff when the retries are more than maxExpoentialRetries and is set to a constant backoff afterward.
  245. retries := d.retries
  246. if retries > maxExpoentialRetries {
  247. retries = maxExpoentialRetries
  248. }
  249. retryTimeInSecond := time.Duration(0x1<<retries) * time.Second
  250. plog.Infof("%s: error connecting to %s, retrying in %s", step, d.url, retryTimeInSecond)
  251. d.clock.Sleep(retryTimeInSecond)
  252. }
  253. func (d *discovery) checkClusterRetry() ([]*client.Node, int, uint64, error) {
  254. if d.retries < nRetries {
  255. d.logAndBackoffForRetry("cluster status check")
  256. return d.checkCluster()
  257. }
  258. return nil, 0, 0, ErrTooManyRetries
  259. }
  260. func (d *discovery) waitNodesRetry() ([]*client.Node, error) {
  261. if d.retries < nRetries {
  262. d.logAndBackoffForRetry("waiting for other nodes")
  263. nodes, n, index, err := d.checkCluster()
  264. if err != nil {
  265. return nil, err
  266. }
  267. return d.waitNodes(nodes, n, index)
  268. }
  269. return nil, ErrTooManyRetries
  270. }
  271. func (d *discovery) waitNodes(nodes []*client.Node, size int, index uint64) ([]*client.Node, error) {
  272. if len(nodes) > size {
  273. nodes = nodes[:size]
  274. }
  275. // watch from the next index
  276. w := d.c.Watcher(d.cluster, &client.WatcherOptions{AfterIndex: index, Recursive: true})
  277. all := make([]*client.Node, len(nodes))
  278. copy(all, nodes)
  279. for _, n := range all {
  280. if path.Base(n.Key) == path.Base(d.selfKey()) {
  281. plog.Noticef("found self %s in the cluster", path.Base(d.selfKey()))
  282. } else {
  283. plog.Noticef("found peer %s in the cluster", path.Base(n.Key))
  284. }
  285. }
  286. // wait for others
  287. for len(all) < size {
  288. plog.Noticef("found %d peer(s), waiting for %d more", len(all), size-len(all))
  289. resp, err := w.Next(context.Background())
  290. if err != nil {
  291. if ce, ok := err.(*client.ClusterError); ok {
  292. plog.Error(ce.Detail())
  293. return d.waitNodesRetry()
  294. }
  295. return nil, err
  296. }
  297. plog.Noticef("found peer %s in the cluster", path.Base(resp.Node.Key))
  298. all = append(all, resp.Node)
  299. }
  300. plog.Noticef("found %d needed peer(s)", len(all))
  301. return all, nil
  302. }
  303. func (d *discovery) selfKey() string {
  304. return path.Join("/", d.cluster, d.id.String())
  305. }
  306. func nodesToCluster(ns []*client.Node, size int) (string, error) {
  307. s := make([]string, len(ns))
  308. for i, n := range ns {
  309. s[i] = n.Value
  310. }
  311. us := strings.Join(s, ",")
  312. m, err := types.NewURLsMap(us)
  313. if err != nil {
  314. return us, ErrInvalidURL
  315. }
  316. if m.Len() != size {
  317. return us, ErrDuplicateName
  318. }
  319. return us, nil
  320. }
  321. type sortableNodes struct{ Nodes []*client.Node }
  322. func (ns sortableNodes) Len() int { return len(ns.Nodes) }
  323. func (ns sortableNodes) Less(i, j int) bool {
  324. return ns.Nodes[i].CreatedIndex < ns.Nodes[j].CreatedIndex
  325. }
  326. func (ns sortableNodes) Swap(i, j int) { ns.Nodes[i], ns.Nodes[j] = ns.Nodes[j], ns.Nodes[i] }