func TestHTTPClientDoCancelContextWaitForRoundTrip(t *testing.T) { tr := newFakeTransport() c := &httpClient{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("httpClient.do should not have exited yet") default: } tr.finishCancel <- struct{}{} select { case <-donechan: //expected behavior return case <-time.After(time.Second): t.Fatalf("httpClient.do did not exit within 1s") } }
// 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{ ID: GenID(), Method: "PUT", Path: path.Join(memberStoreKey(s.id), attributesSuffix), 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 %x", 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) } } }
func TestHTTPClientDoError(t *testing.T) { tr := newFakeTransport() c := &httpClient{transport: tr} tr.errchan <- errors.New("fixture") _, _, err := c.do(context.Background(), &fakeAction{}) if err == nil { t.Fatalf("expected non-nil error, got nil") } }
func TestHTTPClientDoCancelContext(t *testing.T) { tr := newFakeTransport() c := &httpClient{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") } }
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 }
func (h *keysHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if !allowMethod(w, r.Method, "GET", "PUT", "POST", "DELETE") { return } cid := strconv.FormatUint(h.clusterInfo.ID(), 16) w.Header().Set("X-Etcd-Cluster-ID", cid) ctx, cancel := context.WithTimeout(context.Background(), h.timeout) defer cancel() rr, err := parseKeyRequest(r, etcdserver.GenID(), clockwork.NewRealClock()) if err != nil { writeError(w, err) 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!")) } }
// 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: GenID(), 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.node.Propose(ctx, data) cancel() }() }
func (hw *httpWatcher) Next() (*Response, error) { //TODO(bcwaldon): This needs to be cancellable by the calling user httpresp, body, err := hw.client.do(context.Background(), &hw.nextWait) if err != nil { return nil, err } resp, err := unmarshalHTTPResponse(httpresp.StatusCode, body) if err != nil { return nil, err } hw.nextWait.WaitIndex = resp.Node.ModifiedIndex + 1 return resp, nil }
// TestCompacts ensures Node.Compact creates a correct raft snapshot and compacts // the raft log (call raft.compact) func TestNodeCompact(t *testing.T) { ctx := context.Background() n := newNode() r := newRaft(1, []uint64{1}, 10, 1) go n.run(r) n.Campaign(ctx) n.Propose(ctx, []byte("foo")) w := raftpb.Snapshot{ Term: 1, Index: 2, // one nop + one proposal Data: []byte("a snapshot"), Nodes: []uint64{1}, } testutil.ForceGosched() select { case <-n.Ready(): default: t.Fatalf("unexpected proposal failure: unable to commit entry") } n.Compact(w.Index, w.Nodes, w.Data) testutil.ForceGosched() select { case rd := <-n.Ready(): if !reflect.DeepEqual(rd.Snapshot, w) { t.Errorf("snap = %+v, want %+v", rd.Snapshot, w) } default: t.Fatalf("unexpected compact failure: unable to create a snapshot") } testutil.ForceGosched() // TODO: this test the run updates the snapi correctly... should be tested // separately with other kinds of updates select { case <-n.Ready(): t.Fatalf("unexpected more ready") default: } n.Stop() if r.raftLog.offset != w.Index { t.Errorf("log.offset = %d, want %d", r.raftLog.offset, w.Index) } }
func TestNode(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, Nodes: []uint64{1}, RaftState: StateLeader}, HardState: raftpb.HardState{Term: 1, Commit: 2}, Entries: []raftpb.Entry{ {}, {Type: raftpb.EntryConfChange, Term: 1, Index: 1, Data: ccdata}, {Term: 1, Index: 2}, }, CommittedEntries: []raftpb.Entry{ {Type: raftpb.EntryConfChange, Term: 1, Index: 1, Data: ccdata}, {Term: 1, Index: 2}, }, }, { HardState: raftpb.HardState{Term: 1, Commit: 3}, Entries: []raftpb.Entry{{Term: 1, Index: 3, Data: []byte("foo")}}, CommittedEntries: []raftpb.Entry{{Term: 1, Index: 3, Data: []byte("foo")}}, }, } n := StartNode(1, []Peer{{ID: 1}}, 10, 1) n.ApplyConfChange(cc) n.Campaign(ctx) if g := <-n.Ready(); !reflect.DeepEqual(g, wants[0]) { t.Errorf("#%d: g = %+v,\n w %+v", 1, g, wants[0]) } n.Propose(ctx, []byte("foo")) if g := <-n.Ready(); !reflect.DeepEqual(g, wants[1]) { t.Errorf("#%d: g = %+v,\n w %+v", 2, g, wants[1]) } select { case rd := <-n.Ready(): t.Errorf("unexpected Ready: %+v", rd) default: } }
func BenchmarkOneNode(b *testing.B) { ctx, cancel := context.WithCancel(context.Background()) defer cancel() n := StartNode(1, []Peer{{ID: 1}}, 0, 0) defer n.Stop() n.Campaign(ctx) for i := 0; i < b.N; i++ { <-n.Ready() n.Propose(ctx, []byte("foo")) } rd := <-n.Ready() if rd.HardState.Commit != uint64(b.N+1) { b.Errorf("commit = %d, want %d", rd.HardState.Commit, b.N+1) } }
// Cancel and Stop should unblock Step() func TestNodeStepUnblock(t *testing.T) { // a node without buffer to block step n := &node{ propc: make(chan raftpb.Message), done: make(chan struct{}), } ctx, cancel := context.WithCancel(context.Background()) stopFunc := func() { close(n.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 := n.Step(ctx, 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 <-n.done: n.done = make(chan struct{}) default: } case <-time.After(time.Millisecond * 100): t.Errorf("#%d: failed to unblock step", i) } } }
func TestHTTPClientDoSuccess(t *testing.T) { tr := newFakeTransport() c := &httpClient{transport: tr} tr.respchan <- &http.Response{ StatusCode: http.StatusTeapot, Body: ioutil.NopCloser(strings.NewReader("foo")), } resp, body, err := c.do(context.Background(), &fakeAction{}) if err != nil { t.Fatalf("incorrect error value: want=nil got=%v", err) } wantCode := http.StatusTeapot if wantCode != resp.StatusCode { t.Fatalf("invalid response code: want=%d got=%d", wantCode, resp.StatusCode) } wantBody := []byte("foo") if !reflect.DeepEqual(wantBody, body) { t.Fatalf("invalid response body: want=%q got=%q", wantBody, body) } }
func (c *httpClient) doWithTimeout(act httpAction) (*http.Response, []byte, error) { ctx, cancel := context.WithTimeout(context.Background(), c.timeout) defer cancel() return c.do(ctx, act) }
func (h *adminMembersHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if !allowMethod(w, r.Method, "GET", "POST", "DELETE") { return } cid := strconv.FormatUint(h.clusterInfo.ID(), 16) w.Header().Set("X-Etcd-Cluster-ID", cid) ctx, cancel := context.WithTimeout(context.Background(), defaultServerTimeout) defer cancel() switch r.Method { case "GET": if trimPrefix(r.URL.Path, adminMembersPrefix) != "" { writeError(w, httptypes.NewHTTPError(http.StatusNotFound, "Not found")) return } mc := newMemberCollection(h.clusterInfo.Members()) w.Header().Set("Content-Type", "application/json") if err := json.NewEncoder(w).Encode(mc); err != nil { log.Printf("etcdhttp: %v", err) } case "POST": ctype := r.Header.Get("Content-Type") if ctype != "application/json" { writeError(w, httptypes.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Bad Content-Type %s, accept application/json", ctype))) return } b, err := ioutil.ReadAll(r.Body) if err != nil { writeError(w, httptypes.NewHTTPError(http.StatusBadRequest, err.Error())) return } raftAttr := etcdserver.RaftAttributes{} if err := json.Unmarshal(b, &raftAttr); err != nil { writeError(w, httptypes.NewHTTPError(http.StatusBadRequest, err.Error())) return } validURLs, err := types.NewURLs(raftAttr.PeerURLs) if err != nil { writeError(w, httptypes.NewHTTPError(http.StatusBadRequest, "Bad peer urls")) return } now := h.clock.Now() m := etcdserver.NewMember("", validURLs, "", &now) if err := h.server.AddMember(ctx, *m); err != nil { log.Printf("etcdhttp: error adding node %x: %v", m.ID, err) writeError(w, err) return } log.Printf("etcdhttp: added node %x with peer urls %v", m.ID, raftAttr.PeerURLs) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) if err := json.NewEncoder(w).Encode(m); err != nil { log.Printf("etcdhttp: %v", err) } case "DELETE": idStr := trimPrefix(r.URL.Path, adminMembersPrefix) if idStr == "" { http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) return } id, err := strconv.ParseUint(idStr, 16, 64) if err != nil { writeError(w, httptypes.NewHTTPError(http.StatusBadRequest, err.Error())) return } log.Printf("etcdhttp: remove node %x", id) if err := h.server.RemoveMember(ctx, id); err != nil { log.Printf("etcdhttp: error removing node %x: %v", id, err) writeError(w, err) return } w.WriteHeader(http.StatusNoContent) } }