discovery.go 9.1 KB

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