コード例 #1
0
ファイル: discovery.go プロジェクト: rfrangio/etcd-starter
func (d *discovery) checkCluster() ([]*client.Node, int, uint64, error) {
	configKey := path.Join("/", d.cluster, "_config")
	ctx, cancel := context.WithTimeout(context.Background(), client.DefaultRequestTimeout)
	// find cluster size
	resp, err := d.c.Get(ctx, path.Join(configKey, "size"), nil)
	cancel()
	if err != nil {
		if eerr, ok := err.(*client.Error); ok && eerr.Code == client.ErrorCodeKeyNotFound {
			return nil, 0, 0, ErrSizeNotFound
		}
		if err == context.DeadlineExceeded {
			return d.checkClusterRetry()
		}
		return nil, 0, 0, err
	}
	size, err := strconv.Atoi(resp.Node.Value)
	if err != nil {
		return nil, 0, 0, ErrBadSizeKey
	}

	ctx, cancel = context.WithTimeout(context.Background(), client.DefaultRequestTimeout)
	resp, err = d.c.Get(ctx, d.cluster, nil)
	cancel()
	if err != nil {
		if err == context.DeadlineExceeded {
			return d.checkClusterRetry()
		}
		return nil, 0, 0, err
	}
	nodes := make([]*client.Node, 0)
	// append non-config keys to nodes
	for _, n := range resp.Node.Nodes {
		if !(path.Base(n.Key) == path.Base(configKey)) {
			nodes = append(nodes, n)
		}
	}

	snodes := sortableNodes{nodes}
	sort.Sort(snodes)

	// find self position
	for i := range nodes {
		if path.Base(nodes[i].Key) == path.Base(d.selfKey()) {
			break
		}
		if i >= size-1 {
			return nodes[:size], size, resp.Index, ErrFullCluster
		}
	}
	return nodes, size, resp.Index, nil
}
コード例 #2
0
ファイル: server.go プロジェクト: rfrangio/etcd-starter
// publish registers server information into the cluster. The information
// is the JSON representation of this server's member struct, updated with the
// static clientURLs of the server.
// The function keeps attempting to register until it succeeds,
// or its server is stopped.
func (s *EtcdServer) publish(retryInterval time.Duration) {
	b, err := json.Marshal(s.attributes)
	if err != nil {
		log.Printf("etcdserver: json marshal error: %v", err)
		return
	}
	req := pb.Request{
		Method: "PUT",
		Path:   MemberAttributesStorePath(s.id),
		Val:    string(b),
	}

	for {
		ctx, cancel := context.WithTimeout(context.Background(), retryInterval)
		_, err := s.Do(ctx, req)
		cancel()
		switch err {
		case nil:
			log.Printf("etcdserver: published %+v to cluster %s", s.attributes, s.Cluster.ID())
			return
		case ErrStopped:
			log.Printf("etcdserver: aborting publish because server is stopped")
			return
		default:
			log.Printf("etcdserver: publish error: %v", err)
		}
	}
}
コード例 #3
0
ファイル: keys_test.go プロジェクト: rfrangio/etcd-starter
func TestHTTPKeysAPISetResponse(t *testing.T) {
	client := &staticHTTPClient{
		resp: http.Response{
			StatusCode: http.StatusOK,
			Header:     http.Header{"X-Etcd-Index": []string{"21"}},
		},
		body: []byte(`{"action":"set","node":{"key":"/pants/foo/bar/baz","value":"snarf","modifiedIndex":21,"createdIndex":21},"prevNode":{"key":"/pants/foo/bar/baz","value":"snazz","modifiedIndex":20,"createdIndex":19}}`),
	}

	wantResponse := &Response{
		Action:   "set",
		Node:     &Node{Key: "/pants/foo/bar/baz", Value: "snarf", CreatedIndex: uint64(21), ModifiedIndex: uint64(21)},
		PrevNode: &Node{Key: "/pants/foo/bar/baz", Value: "snazz", CreatedIndex: uint64(19), ModifiedIndex: uint64(20)},
		Index:    uint64(21),
	}

	kAPI := &httpKeysAPI{client: client, prefix: "/pants"}
	resp, err := kAPI.Set(context.Background(), "/foo/bar/baz", "snarf", nil)
	if err != nil {
		t.Errorf("non-nil error: %#v", err)
	}
	if !reflect.DeepEqual(wantResponse, resp) {
		t.Errorf("incorrect Response: want=%#v got=%#v", wantResponse, resp)
	}
}
コード例 #4
0
ファイル: members_test.go プロジェクト: rfrangio/etcd-starter
func TestHTTPMembersAPIAddSuccess(t *testing.T) {
	wantAction := &membersAPIActionAdd{
		peerURLs: types.URLs([]url.URL{
			url.URL{Scheme: "http", Host: "127.0.0.1:7002"},
		}),
	}

	mAPI := &httpMembersAPI{
		client: &actionAssertingHTTPClient{
			t:   t,
			act: wantAction,
			resp: http.Response{
				StatusCode: http.StatusCreated,
			},
			body: []byte(`{"id":"94088180e21eb87b","peerURLs":["http://127.0.0.1:7002"]}`),
		},
	}

	wantResponseMember := &Member{
		ID:       "94088180e21eb87b",
		PeerURLs: []string{"http://127.0.0.1:7002"},
	}

	m, err := mAPI.Add(context.Background(), "http://127.0.0.1:7002")
	if err != nil {
		t.Errorf("got non-nil err: %#v", err)
	}
	if !reflect.DeepEqual(wantResponseMember, m) {
		t.Errorf("incorrect Member: want=%#v got=%#v", wantResponseMember, m)
	}
}
コード例 #5
0
ファイル: members_test.go プロジェクト: rfrangio/etcd-starter
func TestHTTPMembersAPIListSuccess(t *testing.T) {
	wantAction := &membersAPIActionList{}
	mAPI := &httpMembersAPI{
		client: &actionAssertingHTTPClient{
			t:   t,
			act: wantAction,
			resp: http.Response{
				StatusCode: http.StatusOK,
			},
			body: []byte(`{"members":[{"id":"94088180e21eb87b","name":"node2","peerURLs":["http://127.0.0.1:7002"],"clientURLs":["http://127.0.0.1:4002"]}]}`),
		},
	}

	wantResponseMembers := []Member{
		Member{
			ID:         "94088180e21eb87b",
			Name:       "node2",
			PeerURLs:   []string{"http://127.0.0.1:7002"},
			ClientURLs: []string{"http://127.0.0.1:4002"},
		},
	}

	m, err := mAPI.List(context.Background())
	if err != nil {
		t.Errorf("got non-nil err: %#v", err)
	}
	if !reflect.DeepEqual(wantResponseMembers, m) {
		t.Errorf("incorrect Members: want=%#v got=%#v", wantResponseMembers, m)
	}
}
コード例 #6
0
ファイル: members_test.go プロジェクト: rfrangio/etcd-starter
func TestHTTPMembersAPIListError(t *testing.T) {
	tests := []httpClient{
		// generic httpClient failure
		&staticHTTPClient{err: errors.New("fail!")},

		// unrecognized HTTP status code
		&staticHTTPClient{
			resp: http.Response{StatusCode: http.StatusTeapot},
		},

		// fail to unmarshal body on StatusOK
		&staticHTTPClient{
			resp: http.Response{
				StatusCode: http.StatusOK,
			},
			body: []byte(`[{"id":"XX`),
		},
	}

	for i, tt := range tests {
		mAPI := &httpMembersAPI{client: tt}
		ms, err := mAPI.List(context.Background())
		if err == nil {
			t.Errorf("#%d: got nil err", i)
		}
		if ms != nil {
			t.Errorf("#%d: got non-nil Member slice", i)
		}
	}
}
コード例 #7
0
ファイル: keys_test.go プロジェクト: rfrangio/etcd-starter
func TestHTTPKeysAPIGetResponse(t *testing.T) {
	client := &staticHTTPClient{
		resp: http.Response{
			StatusCode: http.StatusOK,
			Header:     http.Header{"X-Etcd-Index": []string{"42"}},
		},
		body: []byte(`{"action":"get","node":{"key":"/pants/foo/bar","modifiedIndex":25,"createdIndex":19,"nodes":[{"key":"/pants/foo/bar/baz","value":"snarf","createdIndex":21,"modifiedIndex":25}]}}`),
	}

	wantResponse := &Response{
		Action: "get",
		Node: &Node{
			Key: "/pants/foo/bar",
			Nodes: []*Node{
				&Node{Key: "/pants/foo/bar/baz", Value: "snarf", CreatedIndex: 21, ModifiedIndex: 25},
			},
			CreatedIndex:  uint64(19),
			ModifiedIndex: uint64(25),
		},
		Index: uint64(42),
	}

	kAPI := &httpKeysAPI{client: client, prefix: "/pants"}
	resp, err := kAPI.Get(context.Background(), "/foo/bar", &GetOptions{Recursive: true})
	if err != nil {
		t.Errorf("non-nil error: %#v", err)
	}
	if !reflect.DeepEqual(wantResponse, resp) {
		t.Errorf("incorrect Response: want=%#v got=%#v", wantResponse, resp)
	}
}
コード例 #8
0
ファイル: client_test.go プロジェクト: rfrangio/etcd-starter
func TestHTTPClusterClientSyncFail(t *testing.T) {
	cf := newStaticHTTPClientFactory([]staticHTTPResponse{
		staticHTTPResponse{err: errors.New("fail!")},
	})

	hc := &httpClusterClient{clientFactory: cf}
	err := hc.reset([]string{"http://127.0.0.1:4001"})
	if err != nil {
		t.Fatalf("unexpected error during setup: %#v", err)
	}

	want := []string{"http://127.0.0.1:4001"}
	got := hc.Endpoints()
	if !reflect.DeepEqual(want, got) {
		t.Fatalf("incorrect endpoints: want=%#v got=%#v", want, got)
	}

	err = hc.Sync(context.Background())
	if err == nil {
		t.Fatalf("got nil error during Sync")
	}

	got = hc.Endpoints()
	if !reflect.DeepEqual(want, got) {
		t.Fatalf("incorrect endpoints after failed Sync: want=%#v got=%#v", want, got)
	}
}
コード例 #9
0
ファイル: discovery.go プロジェクト: rfrangio/etcd-starter
func (d *discovery) createSelf(contents string) error {
	ctx, cancel := context.WithTimeout(context.Background(), client.DefaultRequestTimeout)
	resp, err := d.c.Create(ctx, d.selfKey(), contents)
	cancel()
	if err != nil {
		if eerr, ok := err.(*client.Error); ok && eerr.Code == client.ErrorCodeNodeExist {
			return ErrDuplicateID
		}
		return err
	}

	// ensure self appears on the server we connected to
	w := d.c.Watcher(d.selfKey(), &client.WatcherOptions{AfterIndex: resp.Node.CreatedIndex - 1})
	_, err = w.Next(context.Background())
	return err
}
コード例 #10
0
ファイル: client_test.go プロジェクト: rfrangio/etcd-starter
func TestSimpleHTTPClientDoCancelContextWaitForRoundTrip(t *testing.T) {
	tr := newFakeTransport()
	c := &simpleHTTPClient{transport: tr}

	donechan := make(chan struct{})
	ctx, cancel := context.WithCancel(context.Background())
	go func() {
		c.Do(ctx, &fakeAction{})
		close(donechan)
	}()

	// This should call CancelRequest and begin the cancellation process
	cancel()

	select {
	case <-donechan:
		t.Fatalf("simpleHTTPClient.Do should not have exited yet")
	default:
	}

	tr.finishCancel <- struct{}{}

	select {
	case <-donechan:
		//expected behavior
		return
	case <-time.After(time.Second):
		t.Fatalf("simpleHTTPClient.Do did not exit within 1s")
	}
}
コード例 #11
0
ファイル: client_test.go プロジェクト: rfrangio/etcd-starter
func TestSimpleHTTPClientDoCancelContextResponseBodyClosed(t *testing.T) {
	tr := newFakeTransport()
	c := &simpleHTTPClient{transport: tr}

	// create an already-cancelled context
	ctx, cancel := context.WithCancel(context.Background())
	cancel()

	body := &checkableReadCloser{ReadCloser: ioutil.NopCloser(strings.NewReader("foo"))}
	go func() {
		// wait for CancelRequest to be called, informing us that simpleHTTPClient
		// knows the context is already timed out
		<-tr.startCancel

		tr.respchan <- &http.Response{Body: body}
		tr.finishCancel <- struct{}{}
	}()

	_, _, err := c.Do(ctx, &fakeAction{})
	if err == nil {
		t.Fatalf("expected non-nil error, got nil")
	}

	if !body.closed {
		t.Fatalf("expected closed body")
	}
}
コード例 #12
0
ファイル: discovery.go プロジェクト: rfrangio/etcd-starter
func (d *discovery) waitNodes(nodes []*client.Node, size int, index uint64) ([]*client.Node, error) {
	if len(nodes) > size {
		nodes = nodes[:size]
	}
	// watch from the next index
	w := d.c.Watcher(d.cluster, &client.WatcherOptions{AfterIndex: index, Recursive: true})
	all := make([]*client.Node, len(nodes))
	copy(all, nodes)
	for _, n := range all {
		if path.Base(n.Key) == path.Base(d.selfKey()) {
			log.Printf("discovery: found self %s in the cluster", path.Base(d.selfKey()))
		} else {
			log.Printf("discovery: found peer %s in the cluster", path.Base(n.Key))
		}
	}

	// wait for others
	for len(all) < size {
		log.Printf("discovery: found %d peer(s), waiting for %d more", len(all), size-len(all))
		resp, err := w.Next(context.Background())
		if err != nil {
			if err == context.DeadlineExceeded {
				return d.waitNodesRetry()
			}
			return nil, err
		}
		log.Printf("discovery: found peer %s in the cluster", path.Base(resp.Node.Key))
		all = append(all, resp.Node)
	}
	log.Printf("discovery: found %d needed peer(s)", len(all))
	return all, nil
}
コード例 #13
0
ファイル: keys_test.go プロジェクト: rfrangio/etcd-starter
func TestHTTPKeysAPIGetError(t *testing.T) {
	tests := []httpClient{
		// generic HTTP client failure
		&staticHTTPClient{
			err: errors.New("fail!"),
		},

		// unusable status code
		&staticHTTPClient{
			resp: http.Response{
				StatusCode: http.StatusTeapot,
			},
		},

		// etcd Error response
		&staticHTTPClient{
			resp: http.Response{
				StatusCode: http.StatusInternalServerError,
			},
			body: []byte(`{"errorCode":300,"message":"Raft internal error","cause":"/foo","index":18}`),
		},
	}

	for i, tt := range tests {
		kAPI := httpKeysAPI{client: tt}
		resp, err := kAPI.Get(context.Background(), "/foo", nil)
		if err == nil {
			t.Errorf("#%d: received nil error", i)
		}
		if resp != nil {
			t.Errorf("#%d: received non-nil Response: %#v", i, resp)
		}
	}
}
コード例 #14
0
// TestMultiNodeStart ensures that a node can be started correctly. The node should
// start with correct configuration change entries, and can accept and commit
// proposals.
func TestMultiNodeStart(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	cc := raftpb.ConfChange{Type: raftpb.ConfChangeAddNode, NodeID: 1}
	ccdata, err := cc.Marshal()
	if err != nil {
		t.Fatalf("unexpected marshal error: %v", err)
	}
	wants := []Ready{
		{
			SoftState: &SoftState{Lead: 1, RaftState: StateLeader},
			HardState: raftpb.HardState{Term: 2, Commit: 2, Vote: 1},
			Entries: []raftpb.Entry{
				{Type: raftpb.EntryConfChange, Term: 1, Index: 1, Data: ccdata},
				{Term: 2, Index: 2},
			},
			CommittedEntries: []raftpb.Entry{
				{Type: raftpb.EntryConfChange, Term: 1, Index: 1, Data: ccdata},
				{Term: 2, Index: 2},
			},
		},
		{
			HardState:        raftpb.HardState{Term: 2, Commit: 3, Vote: 1},
			Entries:          []raftpb.Entry{{Term: 2, Index: 3, Data: []byte("foo")}},
			CommittedEntries: []raftpb.Entry{{Term: 2, Index: 3, Data: []byte("foo")}},
		},
	}
	mn := StartMultiNode(1, 10, 1)
	storage := NewMemoryStorage()
	mn.CreateGroup(1, []Peer{{ID: 1}}, storage)
	mn.Campaign(ctx, 1)
	gs := <-mn.Ready()
	g := gs[1]
	if !reflect.DeepEqual(g, wants[0]) {
		t.Fatalf("#%d: g = %+v,\n             w   %+v", 1, g, wants[0])
	} else {
		storage.Append(g.Entries)
		mn.Advance(gs)
	}

	mn.Propose(ctx, 1, []byte("foo"))
	if gs2 := <-mn.Ready(); !reflect.DeepEqual(gs2[1], wants[1]) {
		t.Errorf("#%d: g = %+v,\n             w   %+v", 2, gs2[1], wants[1])
	} else {
		storage.Append(gs2[1].Entries)
		mn.Advance(gs2)
	}

	select {
	case rd := <-mn.Ready():
		t.Errorf("unexpected Ready: %+v", rd)
	case <-time.After(time.Millisecond):
	}
}
コード例 #15
0
ファイル: client.go プロジェクト: rfrangio/etcd-starter
func (h *keysHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if !allowMethod(w, r.Method, "HEAD", "GET", "PUT", "POST", "DELETE") {
		return
	}

	w.Header().Set("X-Etcd-Cluster-ID", h.clusterInfo.ID().String())

	ctx, cancel := context.WithTimeout(context.Background(), h.timeout)
	defer cancel()

	rr, err := parseKeyRequest(r, clockwork.NewRealClock())
	if err != nil {
		writeError(w, err)
		return
	}
	// The path must be valid at this point (we've parsed the request successfully).
	if !hasKeyPrefixAccess(h.sec, r, r.URL.Path[len(keysPrefix):]) {
		writeNoAuth(w)
		return
	}

	resp, err := h.server.Do(ctx, rr)
	if err != nil {
		err = trimErrorPrefix(err, etcdserver.StoreKeysPrefix)
		writeError(w, err)
		return
	}

	switch {
	case resp.Event != nil:
		if err := writeKeyEvent(w, resp.Event, h.timer); err != nil {
			// Should never be reached
			log.Printf("error writing event: %v", err)
		}
	case resp.Watcher != nil:
		ctx, cancel := context.WithTimeout(context.Background(), defaultWatchTimeout)
		defer cancel()
		handleKeyWatch(ctx, w, resp.Watcher, rr.Stream, h.timer)
	default:
		writeError(w, errors.New("received response with no Event/Watcher!"))
	}
}
コード例 #16
0
ファイル: client_test.go プロジェクト: rfrangio/etcd-starter
func TestSimpleHTTPClientDoError(t *testing.T) {
	tr := newFakeTransport()
	c := &simpleHTTPClient{transport: tr}

	tr.errchan <- errors.New("fixture")

	_, _, err := c.Do(context.Background(), &fakeAction{})
	if err == nil {
		t.Fatalf("expected non-nil error, got nil")
	}
}
コード例 #17
0
func (s *Store) requestResource(res string, dir bool) (etcdserver.Response, error) {
	ctx, cancel := context.WithTimeout(context.Background(), s.timeout)
	defer cancel()
	p := path.Join(StorePermsPrefix, res)
	rr := etcdserverpb.Request{
		Method: "GET",
		Path:   p,
		Dir:    dir,
	}
	return s.server.Do(ctx, rr)
}
コード例 #18
0
func (s *Store) deleteResource(res string) (etcdserver.Response, error) {
	ctx, cancel := context.WithTimeout(context.Background(), s.timeout)
	defer cancel()
	pex := true
	p := path.Join(StorePermsPrefix, res)
	rr := etcdserverpb.Request{
		Method:    "DELETE",
		Path:      p,
		PrevExist: &pex,
	}
	return s.server.Do(ctx, rr)
}
コード例 #19
0
ファイル: client_test.go プロジェクト: rfrangio/etcd-starter
func TestSimpleHTTPClientDoCancelContext(t *testing.T) {
	tr := newFakeTransport()
	c := &simpleHTTPClient{transport: tr}

	tr.startCancel <- struct{}{}
	tr.finishCancel <- struct{}{}

	_, _, err := c.Do(context.Background(), &fakeAction{})
	if err == nil {
		t.Fatalf("expected non-nil error, got nil")
	}
}
コード例 #20
0
ファイル: keys_test.go プロジェクト: rfrangio/etcd-starter
func TestHTTPKeysAPIDeleteAction(t *testing.T) {
	tests := []struct {
		key        string
		value      string
		opts       *DeleteOptions
		wantAction httpAction
	}{
		// nil DeleteOptions
		{
			key:  "/foo",
			opts: nil,
			wantAction: &deleteAction{
				Key:       "/foo",
				PrevValue: "",
				PrevIndex: 0,
				Recursive: false,
			},
		},
		// empty DeleteOptions
		{
			key:  "/foo",
			opts: &DeleteOptions{},
			wantAction: &deleteAction{
				Key:       "/foo",
				PrevValue: "",
				PrevIndex: 0,
				Recursive: false,
			},
		},
		// populated DeleteOptions
		{
			key: "/foo",
			opts: &DeleteOptions{
				PrevValue: "baz",
				PrevIndex: 13,
				Recursive: true,
			},
			wantAction: &deleteAction{
				Key:       "/foo",
				PrevValue: "baz",
				PrevIndex: 13,
				Recursive: true,
			},
		},
	}

	for i, tt := range tests {
		client := &actionAssertingHTTPClient{t: t, num: i, act: tt.wantAction}
		kAPI := httpKeysAPI{client: client}
		kAPI.Delete(context.Background(), tt.key, tt.opts)
	}
}
コード例 #21
0
ファイル: keys_test.go プロジェクト: rfrangio/etcd-starter
func TestHTTPKeysAPIUpdateAction(t *testing.T) {
	act := &setAction{
		Key:       "/foo",
		Value:     "bar",
		PrevExist: PrevExist,
		PrevIndex: 0,
		PrevValue: "",
		TTL:       0,
	}

	kAPI := httpKeysAPI{client: &actionAssertingHTTPClient{t: t, act: act}}
	kAPI.Update(context.Background(), "/foo", "bar")
}
コード例 #22
0
func ExampleWithTimeout() {
	// Pass a context with a timeout to tell a blocking function that it
	// should abandon its work after the timeout elapses.
	ctx, _ := context.WithTimeout(context.Background(), 100*time.Millisecond)
	select {
	case <-time.After(200 * time.Millisecond):
		fmt.Println("overslept")
	case <-ctx.Done():
		fmt.Println(ctx.Err()) // prints "context deadline exceeded"
	}
	// Output:
	// context deadline exceeded
}
コード例 #23
0
ファイル: keys_test.go プロジェクト: rfrangio/etcd-starter
func TestHTTPWatcherNextWaitAction(t *testing.T) {
	initAction := waitAction{
		Prefix:    "/pants",
		Key:       "/foo/bar",
		Recursive: true,
		WaitIndex: 19,
	}

	client := &actionAssertingHTTPClient{
		t:   t,
		act: &initAction,
		resp: http.Response{
			StatusCode: http.StatusOK,
			Header:     http.Header{"X-Etcd-Index": []string{"42"}},
		},
		body: []byte(`{"action":"update","node":{"key":"/pants/foo/bar/baz","value":"snarf","modifiedIndex":21,"createdIndex":19},"prevNode":{"key":"/pants/foo/bar/baz","value":"snazz","modifiedIndex":20,"createdIndex":19}}`),
	}

	wantResponse := &Response{
		Action:   "update",
		Node:     &Node{Key: "/pants/foo/bar/baz", Value: "snarf", CreatedIndex: uint64(19), ModifiedIndex: uint64(21)},
		PrevNode: &Node{Key: "/pants/foo/bar/baz", Value: "snazz", CreatedIndex: uint64(19), ModifiedIndex: uint64(20)},
		Index:    uint64(42),
	}

	wantNextWait := waitAction{
		Prefix:    "/pants",
		Key:       "/foo/bar",
		Recursive: true,
		WaitIndex: 22,
	}

	watcher := &httpWatcher{
		client:   client,
		nextWait: initAction,
	}

	resp, err := watcher.Next(context.Background())
	if err != nil {
		t.Errorf("non-nil error: %#v", err)
	}

	if !reflect.DeepEqual(wantResponse, resp) {
		t.Errorf("received incorrect Response: want=%#v got=%#v", wantResponse, resp)
	}

	if !reflect.DeepEqual(wantNextWait, watcher.nextWait) {
		t.Errorf("nextWait incorrect: want=%#v got=%#v", wantNextWait, watcher.nextWait)
	}
}
コード例 #24
0
ファイル: server.go プロジェクト: rfrangio/etcd-starter
// sync proposes a SYNC request and is non-blocking.
// This makes no guarantee that the request will be proposed or performed.
// The request will be cancelled after the given timeout.
func (s *EtcdServer) sync(timeout time.Duration) {
	ctx, cancel := context.WithTimeout(context.Background(), timeout)
	req := pb.Request{
		Method: "SYNC",
		ID:     s.reqIDGen.Next(),
		Time:   time.Now().UnixNano(),
	}
	data := pbutil.MustMarshal(&req)
	// There is no promise that node has leader when do SYNC request,
	// so it uses goroutine to propose.
	go func() {
		s.r.Propose(ctx, data)
		cancel()
	}()
}
コード例 #25
0
ファイル: keys_test.go プロジェクト: rfrangio/etcd-starter
func TestHTTPWatcherNextFail(t *testing.T) {
	tests := []httpClient{
		// generic HTTP client failure
		&staticHTTPClient{
			err: errors.New("fail!"),
		},

		// unusable status code
		&staticHTTPClient{
			resp: http.Response{
				StatusCode: http.StatusTeapot,
			},
		},

		// etcd Error response
		&staticHTTPClient{
			resp: http.Response{
				StatusCode: http.StatusNotFound,
			},
			body: []byte(`{"errorCode":100,"message":"Key not found","cause":"/foo","index":18}`),
		},
	}

	for i, tt := range tests {
		act := waitAction{
			Prefix:    "/pants",
			Key:       "/foo/bar",
			Recursive: true,
			WaitIndex: 19,
		}

		watcher := &httpWatcher{
			client:   tt,
			nextWait: act,
		}

		resp, err := watcher.Next(context.Background())
		if err == nil {
			t.Errorf("#%d: expected non-nil error", i)
		}
		if resp != nil {
			t.Errorf("#%d: expected nil Response, got %#v", i, resp)
		}
		if !reflect.DeepEqual(act, watcher.nextWait) {
			t.Errorf("#%d: nextWait changed: want=%#v got=%#v", i, act, watcher.nextWait)
		}
	}
}
コード例 #26
0
func (s *Store) setResource(res string, value interface{}, prevexist bool) (etcdserver.Response, error) {
	ctx, cancel := context.WithTimeout(context.Background(), s.timeout)
	defer cancel()
	data, err := json.Marshal(value)
	if err != nil {
		return etcdserver.Response{}, err
	}
	p := path.Join(StorePermsPrefix, res)
	rr := etcdserverpb.Request{
		Method:    "PUT",
		Path:      p,
		Val:       string(data),
		PrevExist: &prevexist,
	}
	return s.server.Do(ctx, rr)
}
コード例 #27
0
ファイル: keys_test.go プロジェクト: rfrangio/etcd-starter
func TestHTTPKeysAPIGetAction(t *testing.T) {
	tests := []struct {
		key        string
		opts       *GetOptions
		wantAction httpAction
	}{
		// nil GetOptions
		{
			key:  "/foo",
			opts: nil,
			wantAction: &getAction{
				Key:       "/foo",
				Sorted:    false,
				Recursive: false,
			},
		},
		// empty GetOptions
		{
			key:  "/foo",
			opts: &GetOptions{},
			wantAction: &getAction{
				Key:       "/foo",
				Sorted:    false,
				Recursive: false,
			},
		},
		// populated GetOptions
		{
			key: "/foo",
			opts: &GetOptions{
				Sort:      true,
				Recursive: true,
			},
			wantAction: &getAction{
				Key:       "/foo",
				Sorted:    true,
				Recursive: true,
			},
		},
	}

	for i, tt := range tests {
		client := &actionAssertingHTTPClient{t: t, num: i, act: tt.wantAction}
		kAPI := httpKeysAPI{client: client}
		kAPI.Get(context.Background(), tt.key, tt.opts)
	}
}
コード例 #28
0
// Cancel and Stop should unblock Step()
func TestMultiNodeStepUnblock(t *testing.T) {
	// a node without buffer to block step
	mn := &multiNode{
		propc: make(chan multiMessage),
		done:  make(chan struct{}),
	}

	ctx, cancel := context.WithCancel(context.Background())
	stopFunc := func() { close(mn.done) }

	tests := []struct {
		unblock func()
		werr    error
	}{
		{stopFunc, ErrStopped},
		{cancel, context.Canceled},
	}

	for i, tt := range tests {
		errc := make(chan error, 1)
		go func() {
			err := mn.Step(ctx, 1, raftpb.Message{Type: raftpb.MsgProp})
			errc <- err
		}()
		tt.unblock()
		select {
		case err := <-errc:
			if err != tt.werr {
				t.Errorf("#%d: err = %v, want %v", i, err, tt.werr)
			}
			//clean up side-effect
			if ctx.Err() != nil {
				ctx = context.TODO()
			}
			select {
			case <-mn.done:
				mn.done = make(chan struct{})
			default:
			}
		case <-time.After(time.Millisecond * 100):
			t.Errorf("#%d: failed to unblock step", i)
		}
	}
}
コード例 #29
0
ファイル: members_test.go プロジェクト: rfrangio/etcd-starter
func TestHTTPMembersAPIRemoveSuccess(t *testing.T) {
	wantAction := &membersAPIActionRemove{
		memberID: "94088180e21eb87b",
	}

	mAPI := &httpMembersAPI{
		client: &actionAssertingHTTPClient{
			t:   t,
			act: wantAction,
			resp: http.Response{
				StatusCode: http.StatusNoContent,
			},
		},
	}

	if err := mAPI.Remove(context.Background(), "94088180e21eb87b"); err != nil {
		t.Errorf("got non-nil err: %#v", err)
	}
}
コード例 #30
0
ファイル: client_test.go プロジェクト: rfrangio/etcd-starter
func TestHTTPClusterClientSync(t *testing.T) {
	cf := newStaticHTTPClientFactory([]staticHTTPResponse{
		staticHTTPResponse{
			resp: http.Response{StatusCode: http.StatusOK, Header: http.Header{"Content-Type": []string{"application/json"}}},
			body: []byte(`{"members":[{"id":"2745e2525fce8fe","peerURLs":["http://127.0.0.1:7003"],"name":"node3","clientURLs":["http://127.0.0.1:4003"]},{"id":"42134f434382925","peerURLs":["http://127.0.0.1:2380","http://127.0.0.1:7001"],"name":"node1","clientURLs":["http://127.0.0.1:2379","http://127.0.0.1:4001"]},{"id":"94088180e21eb87b","peerURLs":["http://127.0.0.1:7002"],"name":"node2","clientURLs":["http://127.0.0.1:4002"]}]}`),
		},
	})

	hc := &httpClusterClient{clientFactory: cf}
	err := hc.reset([]string{"http://127.0.0.1:4001"})
	if err != nil {
		t.Fatalf("unexpected error during setup: %#v", err)
	}

	want := []string{"http://127.0.0.1:4001"}
	got := hc.Endpoints()
	if !reflect.DeepEqual(want, got) {
		t.Fatalf("incorrect endpoints: want=%#v got=%#v", want, got)
	}

	err = hc.Sync(context.Background())
	if err != nil {
		t.Fatalf("unexpected error during Sync: %#v", err)
	}

	want = []string{"http://127.0.0.1:4003", "http://127.0.0.1:2379", "http://127.0.0.1:4001", "http://127.0.0.1:4002"}
	got = hc.Endpoints()
	if !reflect.DeepEqual(want, got) {
		t.Fatalf("incorrect endpoints post-Sync: want=%#v got=%#v", want, got)
	}

	err = hc.reset([]string{"http://127.0.0.1:4009"})
	if err != nil {
		t.Fatalf("unexpected error during reset: %#v", err)
	}

	want = []string{"http://127.0.0.1:4009"}
	got = hc.Endpoints()
	if !reflect.DeepEqual(want, got) {
		t.Fatalf("incorrect endpoints post-reset: want=%#v got=%#v", want, got)
	}
}