123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558 |
- // Copyright 2015 The etcd Authors
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- package discovery
- import (
- "errors"
- "math"
- "math/rand"
- "net/http"
- "reflect"
- "sort"
- "strconv"
- "testing"
- "time"
- "github.com/jonboulle/clockwork"
- "golang.org/x/net/context"
- "github.com/coreos/etcd/client"
- )
- const (
- maxRetryInTest = 3
- )
- func TestNewProxyFuncUnset(t *testing.T) {
- pf, err := newProxyFunc("")
- if pf != nil {
- t.Fatal("unexpected non-nil proxyFunc")
- }
- if err != nil {
- t.Fatalf("unexpected non-nil err: %v", err)
- }
- }
- func TestNewProxyFuncBad(t *testing.T) {
- tests := []string{
- "%%",
- "http://foo.com/%1",
- }
- for i, in := range tests {
- pf, err := newProxyFunc(in)
- if pf != nil {
- t.Errorf("#%d: unexpected non-nil proxyFunc", i)
- }
- if err == nil {
- t.Errorf("#%d: unexpected nil err", i)
- }
- }
- }
- func TestNewProxyFunc(t *testing.T) {
- tests := map[string]string{
- "bar.com": "http://bar.com",
- "http://disco.foo.bar": "http://disco.foo.bar",
- }
- for in, w := range tests {
- pf, err := newProxyFunc(in)
- if pf == nil {
- t.Errorf("%s: unexpected nil proxyFunc", in)
- continue
- }
- if err != nil {
- t.Errorf("%s: unexpected non-nil err: %v", in, err)
- continue
- }
- g, err := pf(&http.Request{})
- if err != nil {
- t.Errorf("%s: unexpected non-nil err: %v", in, err)
- }
- if g.String() != w {
- t.Errorf("%s: proxyURL=%q, want %q", in, g, w)
- }
- }
- }
- func TestCheckCluster(t *testing.T) {
- cluster := "/prefix/1000"
- self := "/1000/1"
- tests := []struct {
- nodes []*client.Node
- index uint64
- werr error
- wsize int
- }{
- {
- // self is in the size range
- []*client.Node{
- {Key: "/1000/_config/size", Value: "3", CreatedIndex: 1},
- {Key: "/1000/_config/"},
- {Key: self, CreatedIndex: 2},
- {Key: "/1000/2", CreatedIndex: 3},
- {Key: "/1000/3", CreatedIndex: 4},
- {Key: "/1000/4", CreatedIndex: 5},
- },
- 5,
- nil,
- 3,
- },
- {
- // self is in the size range
- []*client.Node{
- {Key: "/1000/_config/size", Value: "3", CreatedIndex: 1},
- {Key: "/1000/_config/"},
- {Key: "/1000/2", CreatedIndex: 2},
- {Key: "/1000/3", CreatedIndex: 3},
- {Key: self, CreatedIndex: 4},
- {Key: "/1000/4", CreatedIndex: 5},
- },
- 5,
- nil,
- 3,
- },
- {
- // self is out of the size range
- []*client.Node{
- {Key: "/1000/_config/size", Value: "3", CreatedIndex: 1},
- {Key: "/1000/_config/"},
- {Key: "/1000/2", CreatedIndex: 2},
- {Key: "/1000/3", CreatedIndex: 3},
- {Key: "/1000/4", CreatedIndex: 4},
- {Key: self, CreatedIndex: 5},
- },
- 5,
- ErrFullCluster,
- 3,
- },
- {
- // self is not in the cluster
- []*client.Node{
- {Key: "/1000/_config/size", Value: "3", CreatedIndex: 1},
- {Key: "/1000/_config/"},
- {Key: "/1000/2", CreatedIndex: 2},
- {Key: "/1000/3", CreatedIndex: 3},
- },
- 3,
- nil,
- 3,
- },
- {
- []*client.Node{
- {Key: "/1000/_config/size", Value: "3", CreatedIndex: 1},
- {Key: "/1000/_config/"},
- {Key: "/1000/2", CreatedIndex: 2},
- {Key: "/1000/3", CreatedIndex: 3},
- {Key: "/1000/4", CreatedIndex: 4},
- },
- 3,
- ErrFullCluster,
- 3,
- },
- {
- // bad size key
- []*client.Node{
- {Key: "/1000/_config/size", Value: "bad", CreatedIndex: 1},
- },
- 0,
- ErrBadSizeKey,
- 0,
- },
- {
- // no size key
- []*client.Node{},
- 0,
- ErrSizeNotFound,
- 0,
- },
- }
- for i, tt := range tests {
- rs := make([]*client.Response, 0)
- if len(tt.nodes) > 0 {
- rs = append(rs, &client.Response{Node: tt.nodes[0], Index: tt.index})
- rs = append(rs, &client.Response{
- Node: &client.Node{
- Key: cluster,
- Nodes: tt.nodes[1:],
- },
- Index: tt.index,
- })
- }
- c := &clientWithResp{rs: rs}
- dBase := discovery{cluster: cluster, id: 1, c: c}
- cRetry := &clientWithRetry{failTimes: 3}
- cRetry.rs = rs
- fc := clockwork.NewFakeClock()
- dRetry := discovery{cluster: cluster, id: 1, c: cRetry, clock: fc}
- for _, d := range []discovery{dBase, dRetry} {
- go func() {
- for i := uint(1); i <= maxRetryInTest; i++ {
- fc.BlockUntil(1)
- fc.Advance(time.Second * (0x1 << i))
- }
- }()
- ns, size, index, err := d.checkCluster()
- if err != tt.werr {
- t.Errorf("#%d: err = %v, want %v", i, err, tt.werr)
- }
- if reflect.DeepEqual(ns, tt.nodes) {
- t.Errorf("#%d: nodes = %v, want %v", i, ns, tt.nodes)
- }
- if size != tt.wsize {
- t.Errorf("#%d: size = %v, want %d", i, size, tt.wsize)
- }
- if index != tt.index {
- t.Errorf("#%d: index = %v, want %d", i, index, tt.index)
- }
- }
- }
- }
- func TestWaitNodes(t *testing.T) {
- all := []*client.Node{
- 0: {Key: "/1000/1", CreatedIndex: 2},
- 1: {Key: "/1000/2", CreatedIndex: 3},
- 2: {Key: "/1000/3", CreatedIndex: 4},
- }
- tests := []struct {
- nodes []*client.Node
- rs []*client.Response
- }{
- {
- all,
- []*client.Response{},
- },
- {
- all[:1],
- []*client.Response{
- {Node: &client.Node{Key: "/1000/2", CreatedIndex: 3}},
- {Node: &client.Node{Key: "/1000/3", CreatedIndex: 4}},
- },
- },
- {
- all[:2],
- []*client.Response{
- {Node: &client.Node{Key: "/1000/3", CreatedIndex: 4}},
- },
- },
- {
- append(all, &client.Node{Key: "/1000/4", CreatedIndex: 5}),
- []*client.Response{
- {Node: &client.Node{Key: "/1000/3", CreatedIndex: 4}},
- },
- },
- }
- for i, tt := range tests {
- // Basic case
- c := &clientWithResp{rs: nil, w: &watcherWithResp{rs: tt.rs}}
- dBase := &discovery{cluster: "1000", c: c}
- // Retry case
- retryScanResp := make([]*client.Response, 0)
- if len(tt.nodes) > 0 {
- retryScanResp = append(retryScanResp, &client.Response{
- Node: &client.Node{
- Key: "1000",
- Value: strconv.Itoa(3),
- },
- })
- retryScanResp = append(retryScanResp, &client.Response{
- Node: &client.Node{
- Nodes: tt.nodes,
- },
- })
- }
- cRetry := &clientWithResp{
- rs: retryScanResp,
- w: &watcherWithRetry{rs: tt.rs, failTimes: 2},
- }
- fc := clockwork.NewFakeClock()
- dRetry := &discovery{
- cluster: "1000",
- c: cRetry,
- clock: fc,
- }
- for _, d := range []*discovery{dBase, dRetry} {
- go func() {
- for i := uint(1); i <= maxRetryInTest; i++ {
- fc.BlockUntil(1)
- fc.Advance(time.Second * (0x1 << i))
- }
- }()
- g, err := d.waitNodes(tt.nodes, 3, 0) // we do not care about index in this test
- if err != nil {
- t.Errorf("#%d: err = %v, want %v", i, err, nil)
- }
- if !reflect.DeepEqual(g, all) {
- t.Errorf("#%d: all = %v, want %v", i, g, all)
- }
- }
- }
- }
- func TestCreateSelf(t *testing.T) {
- rs := []*client.Response{{Node: &client.Node{Key: "1000/1", CreatedIndex: 2}}}
- w := &watcherWithResp{rs: rs}
- errw := &watcherWithErr{err: errors.New("watch err")}
- c := &clientWithResp{rs: rs, w: w}
- errc := &clientWithErr{err: errors.New("create err"), w: w}
- errdupc := &clientWithErr{err: client.Error{Code: client.ErrorCodeNodeExist}}
- errwc := &clientWithResp{rs: rs, w: errw}
- tests := []struct {
- c client.KeysAPI
- werr error
- }{
- // no error
- {c, nil},
- // client.create returns an error
- {errc, errc.err},
- // watcher.next returns an error
- {errwc, errw.err},
- // parse key exist error to duplicate ID error
- {errdupc, ErrDuplicateID},
- }
- for i, tt := range tests {
- d := discovery{cluster: "1000", c: tt.c}
- if err := d.createSelf(""); err != tt.werr {
- t.Errorf("#%d: err = %v, want %v", i, err, nil)
- }
- }
- }
- func TestNodesToCluster(t *testing.T) {
- tests := []struct {
- nodes []*client.Node
- size int
- wcluster string
- werr error
- }{
- {
- []*client.Node{
- 0: {Key: "/1000/1", Value: "1=http://1.1.1.1:2380", CreatedIndex: 1},
- 1: {Key: "/1000/2", Value: "2=http://2.2.2.2:2380", CreatedIndex: 2},
- 2: {Key: "/1000/3", Value: "3=http://3.3.3.3:2380", CreatedIndex: 3},
- },
- 3,
- "1=http://1.1.1.1:2380,2=http://2.2.2.2:2380,3=http://3.3.3.3:2380",
- nil,
- },
- {
- []*client.Node{
- 0: {Key: "/1000/1", Value: "1=http://1.1.1.1:2380", CreatedIndex: 1},
- 1: {Key: "/1000/2", Value: "2=http://2.2.2.2:2380", CreatedIndex: 2},
- 2: {Key: "/1000/3", Value: "2=http://3.3.3.3:2380", CreatedIndex: 3},
- },
- 3,
- "1=http://1.1.1.1:2380,2=http://2.2.2.2:2380,2=http://3.3.3.3:2380",
- ErrDuplicateName,
- },
- {
- []*client.Node{
- 0: {Key: "/1000/1", Value: "1=1.1.1.1:2380", CreatedIndex: 1},
- 1: {Key: "/1000/2", Value: "2=http://2.2.2.2:2380", CreatedIndex: 2},
- 2: {Key: "/1000/3", Value: "2=http://3.3.3.3:2380", CreatedIndex: 3},
- },
- 3,
- "1=1.1.1.1:2380,2=http://2.2.2.2:2380,2=http://3.3.3.3:2380",
- ErrInvalidURL,
- },
- }
- for i, tt := range tests {
- cluster, err := nodesToCluster(tt.nodes, tt.size)
- if err != tt.werr {
- t.Errorf("#%d: err = %v, want %v", i, err, tt.werr)
- }
- if !reflect.DeepEqual(cluster, tt.wcluster) {
- t.Errorf("#%d: cluster = %v, want %v", i, cluster, tt.wcluster)
- }
- }
- }
- func TestSortableNodes(t *testing.T) {
- ns := []*client.Node{
- 0: {CreatedIndex: 5},
- 1: {CreatedIndex: 1},
- 2: {CreatedIndex: 3},
- 3: {CreatedIndex: 4},
- }
- // add some randomness
- for i := 0; i < 10000; i++ {
- ns = append(ns, &client.Node{CreatedIndex: uint64(rand.Int31())})
- }
- sns := sortableNodes{ns}
- sort.Sort(sns)
- cis := make([]int, 0)
- for _, n := range sns.Nodes {
- cis = append(cis, int(n.CreatedIndex))
- }
- if !sort.IntsAreSorted(cis) {
- t.Errorf("isSorted = %v, want %v", sort.IntsAreSorted(cis), true)
- }
- cis = make([]int, 0)
- for _, n := range ns {
- cis = append(cis, int(n.CreatedIndex))
- }
- if !sort.IntsAreSorted(cis) {
- t.Errorf("isSorted = %v, want %v", sort.IntsAreSorted(cis), true)
- }
- }
- func TestRetryFailure(t *testing.T) {
- nRetries = maxRetryInTest
- defer func() { nRetries = math.MaxUint32 }()
- cluster := "1000"
- c := &clientWithRetry{failTimes: 4}
- fc := clockwork.NewFakeClock()
- d := discovery{
- cluster: cluster,
- id: 1,
- c: c,
- clock: fc,
- }
- go func() {
- for i := uint(1); i <= maxRetryInTest; i++ {
- fc.BlockUntil(1)
- fc.Advance(time.Second * (0x1 << i))
- }
- }()
- if _, _, _, err := d.checkCluster(); err != ErrTooManyRetries {
- t.Errorf("err = %v, want %v", err, ErrTooManyRetries)
- }
- }
- type clientWithResp struct {
- rs []*client.Response
- w client.Watcher
- client.KeysAPI
- }
- func (c *clientWithResp) Create(ctx context.Context, key string, value string) (*client.Response, error) {
- if len(c.rs) == 0 {
- return &client.Response{}, nil
- }
- r := c.rs[0]
- c.rs = c.rs[1:]
- return r, nil
- }
- func (c *clientWithResp) Get(ctx context.Context, key string, opts *client.GetOptions) (*client.Response, error) {
- if len(c.rs) == 0 {
- return &client.Response{}, &client.Error{Code: client.ErrorCodeKeyNotFound}
- }
- r := c.rs[0]
- c.rs = append(c.rs[1:], r)
- return r, nil
- }
- func (c *clientWithResp) Watcher(key string, opts *client.WatcherOptions) client.Watcher {
- return c.w
- }
- type clientWithErr struct {
- err error
- w client.Watcher
- client.KeysAPI
- }
- func (c *clientWithErr) Create(ctx context.Context, key string, value string) (*client.Response, error) {
- return &client.Response{}, c.err
- }
- func (c *clientWithErr) Get(ctx context.Context, key string, opts *client.GetOptions) (*client.Response, error) {
- return &client.Response{}, c.err
- }
- func (c *clientWithErr) Watcher(key string, opts *client.WatcherOptions) client.Watcher {
- return c.w
- }
- type watcherWithResp struct {
- client.KeysAPI
- rs []*client.Response
- }
- func (w *watcherWithResp) Next(context.Context) (*client.Response, error) {
- if len(w.rs) == 0 {
- return &client.Response{}, nil
- }
- r := w.rs[0]
- w.rs = w.rs[1:]
- return r, nil
- }
- type watcherWithErr struct {
- err error
- }
- func (w *watcherWithErr) Next(context.Context) (*client.Response, error) {
- return &client.Response{}, w.err
- }
- // clientWithRetry will timeout all requests up to failTimes
- type clientWithRetry struct {
- clientWithResp
- failCount int
- failTimes int
- }
- func (c *clientWithRetry) Create(ctx context.Context, key string, value string) (*client.Response, error) {
- if c.failCount < c.failTimes {
- c.failCount++
- return nil, &client.ClusterError{Errors: []error{context.DeadlineExceeded}}
- }
- return c.clientWithResp.Create(ctx, key, value)
- }
- func (c *clientWithRetry) Get(ctx context.Context, key string, opts *client.GetOptions) (*client.Response, error) {
- if c.failCount < c.failTimes {
- c.failCount++
- return nil, &client.ClusterError{Errors: []error{context.DeadlineExceeded}}
- }
- return c.clientWithResp.Get(ctx, key, opts)
- }
- // watcherWithRetry will timeout all requests up to failTimes
- type watcherWithRetry struct {
- rs []*client.Response
- failCount int
- failTimes int
- }
- func (w *watcherWithRetry) Next(context.Context) (*client.Response, error) {
- if w.failCount < w.failTimes {
- w.failCount++
- return nil, &client.ClusterError{Errors: []error{context.DeadlineExceeded}}
- }
- if len(w.rs) == 0 {
- return &client.Response{}, nil
- }
- r := w.rs[0]
- w.rs = w.rs[1:]
- return r, nil
- }
|