discovery.go 8.7 KB

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