discovery.go 12 KB

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