From 0c1d1b7aeb8612bb5f9a231e7e5448db2307b417 Mon Sep 17 00:00:00 2001 From: Yicheng Qin Date: Mon, 8 Sep 2014 16:34:29 -0700 Subject: [PATCH 1/5] etcdhttp: add /v2/machines endpoint --- etcdserver/etcdhttp/http.go | 29 +++++++++++++++++++++++++++-- etcdserver/etcdhttp/http_test.go | 32 ++++++++++++++++++++++++++++++++ main.go | 1 + 3 files changed, 60 insertions(+), 2 deletions(-) diff --git a/etcdserver/etcdhttp/http.go b/etcdserver/etcdhttp/http.go index c42bd77bf..c15100310 100644 --- a/etcdserver/etcdhttp/http.go +++ b/etcdserver/etcdhttp/http.go @@ -11,6 +11,7 @@ import ( "log" "net/http" "net/url" + "sort" "strconv" "strings" "time" @@ -27,7 +28,10 @@ import ( "github.com/coreos/etcd/third_party/code.google.com/p/go.net/context" ) -const keysPrefix = "/v2/keys" +const ( + keysPrefix = "/v2/keys" + machinesPrefix = "/v2/machines" +) type Peers map[int64][]string @@ -36,7 +40,12 @@ func (ps Peers) Pick(id int64) string { if len(addrs) == 0 { return "" } - return fmt.Sprintf("http://%s", addrs[rand.Intn(len(addrs))]) + return addScheme(addrs[rand.Intn(len(addrs))]) +} + +// TODO: improve this when implementing TLS +func addScheme(addr string) string { + return fmt.Sprintf("http://%s", addr) } // Set parses command line sets of names to ips formatted like: @@ -138,6 +147,7 @@ func httpPost(url string, data []byte) bool { type Handler struct { Timeout time.Duration Server *etcdserver.Server + Peers Peers } func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { @@ -156,6 +166,8 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { h.serveRaft(ctx, w, r) case strings.HasPrefix(r.URL.Path, keysPrefix): h.serveKeys(ctx, w, r) + case strings.HasPrefix(r.URL.Path, machinesPrefix): + h.serveMachines(w, r) default: http.NotFound(w, r) } @@ -188,6 +200,19 @@ func (h Handler) serveKeys(ctx context.Context, w http.ResponseWriter, r *http.R } } +// serveMachines responds address list in the format '0.0.0.0, 1.1.1.1'. +// TODO: rethink the format of machine list because it is not json format. +func (h Handler) serveMachines(w http.ResponseWriter, r *http.Request) { + urls := make([]string, 0) + for _, addrs := range h.Peers { + for _, addr := range addrs { + urls = append(urls, addScheme(addr)) + } + } + sort.Sort(sort.StringSlice(urls)) + w.Write([]byte(strings.Join(urls, ", "))) +} + func (h Handler) serveRaft(ctx context.Context, w http.ResponseWriter, r *http.Request) { b, err := ioutil.ReadAll(r.Body) if err != nil { diff --git a/etcdserver/etcdhttp/http_test.go b/etcdserver/etcdhttp/http_test.go index 61107e0b1..c0b667279 100644 --- a/etcdserver/etcdhttp/http_test.go +++ b/etcdserver/etcdhttp/http_test.go @@ -347,3 +347,35 @@ func TestWaitForEventCancelledContext(t *testing.T) { t.Fatalf("nil err returned with cancelled context!") } } + +func TestV2MachinesEndpoint(t *testing.T) { + h := Handler{Peers: Peers{}} + + s := httptest.NewServer(h) + defer s.Close() + + resp, err := http.Get(s.URL + machinesPrefix) + if err != nil { + t.Fatal(err) + } + + if resp.StatusCode != http.StatusOK { + t.Errorf("StatusCode = %d, expected %d", resp.StatusCode, http.StatusOK) + } +} + +func TestServeMachines(t *testing.T) { + peers := Peers{} + peers.Set("0xBEEF0=localhost:8080&0xBEEF1=localhost:8081&0xBEEF2=localhost:8082") + h := Handler{Peers: peers} + + writer := httptest.NewRecorder() + h.serveMachines(writer, nil) + w := "http://localhost:8080, http://localhost:8081, http://localhost:8082" + if g := writer.Body.String(); g != w { + t.Errorf("data = %s, want %s", g, w) + } + if writer.Code != http.StatusOK { + t.Errorf("header = %d, want %d", writer.Code, http.StatusOK) + } +} diff --git a/main.go b/main.go index 8e3e5694f..93fcdac2a 100644 --- a/main.go +++ b/main.go @@ -70,6 +70,7 @@ func main() { h := &etcdhttp.Handler{ Timeout: *timeout, Server: s, + Peers: peers, } http.Handle("/", h) log.Fatal(http.ListenAndServe(*laddr, nil)) From 4087fa5c7a777f33df2d17961be84969b9844953 Mon Sep 17 00:00:00 2001 From: Yicheng Qin Date: Tue, 9 Sep 2014 15:22:52 -0700 Subject: [PATCH 2/5] http: allow GET, HEAD for /v2/machines --- etcdserver/etcdhttp/http.go | 4 ++++ etcdserver/etcdhttp/http_test.go | 28 ++++++++++++++++++++-------- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/etcdserver/etcdhttp/http.go b/etcdserver/etcdhttp/http.go index c15100310..5c91592e7 100644 --- a/etcdserver/etcdhttp/http.go +++ b/etcdserver/etcdhttp/http.go @@ -203,6 +203,10 @@ func (h Handler) serveKeys(ctx context.Context, w http.ResponseWriter, r *http.R // serveMachines responds address list in the format '0.0.0.0, 1.1.1.1'. // TODO: rethink the format of machine list because it is not json format. func (h Handler) serveMachines(w http.ResponseWriter, r *http.Request) { + if r.Method != "GET" && r.Method != "HEAD" { + w.WriteHeader(http.StatusMethodNotAllowed) + return + } urls := make([]string, 0) for _, addrs := range h.Peers { for _, addr := range addrs { diff --git a/etcdserver/etcdhttp/http_test.go b/etcdserver/etcdhttp/http_test.go index c0b667279..93d871ba5 100644 --- a/etcdserver/etcdhttp/http_test.go +++ b/etcdserver/etcdhttp/http_test.go @@ -349,18 +349,29 @@ func TestWaitForEventCancelledContext(t *testing.T) { } func TestV2MachinesEndpoint(t *testing.T) { - h := Handler{Peers: Peers{}} + tests := []struct { + method string + wcode int + }{ + {"GET", http.StatusOK}, + {"HEAD", http.StatusOK}, + {"POST", http.StatusMethodNotAllowed}, + } + h := Handler{Peers: Peers{}} s := httptest.NewServer(h) defer s.Close() - resp, err := http.Get(s.URL + machinesPrefix) - if err != nil { - t.Fatal(err) - } + for _, tt := range tests { + req, _ := http.NewRequest(tt.method, s.URL + machinesPrefix, nil) + resp, err := http.DefaultClient.Do(req) + if err != nil { + t.Fatal(err) + } - if resp.StatusCode != http.StatusOK { - t.Errorf("StatusCode = %d, expected %d", resp.StatusCode, http.StatusOK) + if resp.StatusCode != tt.wcode { + t.Errorf("StatusCode = %d, expected %d", resp.StatusCode, tt.wcode) + } } } @@ -370,7 +381,8 @@ func TestServeMachines(t *testing.T) { h := Handler{Peers: peers} writer := httptest.NewRecorder() - h.serveMachines(writer, nil) + req, _ := http.NewRequest("GET", "", nil) + h.serveMachines(writer, req) w := "http://localhost:8080, http://localhost:8081, http://localhost:8082" if g := writer.Body.String(); g != w { t.Errorf("data = %s, want %s", g, w) From 961a61d708234ac0e0e14303260e864d3c2584d3 Mon Sep 17 00:00:00 2001 From: Yicheng Qin Date: Tue, 9 Sep 2014 15:44:34 -0700 Subject: [PATCH 3/5] http: add allow func For further extendability --- etcdserver/etcdhttp/http.go | 8 +++++++- etcdserver/etcdhttp/http_test.go | 4 ++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/etcdserver/etcdhttp/http.go b/etcdserver/etcdhttp/http.go index 5c91592e7..ee7ae6056 100644 --- a/etcdserver/etcdhttp/http.go +++ b/etcdserver/etcdhttp/http.go @@ -204,7 +204,7 @@ func (h Handler) serveKeys(ctx context.Context, w http.ResponseWriter, r *http.R // TODO: rethink the format of machine list because it is not json format. func (h Handler) serveMachines(w http.ResponseWriter, r *http.Request) { if r.Method != "GET" && r.Method != "HEAD" { - w.WriteHeader(http.StatusMethodNotAllowed) + allow(w, "GET", "HEAD") return } urls := make([]string, 0) @@ -349,3 +349,9 @@ func waitForEvent(ctx context.Context, w http.ResponseWriter, wa store.Watcher) return nil, ctx.Err() } } + +// allow writes response for the case that Method Not Allowed +func allow(w http.ResponseWriter, m ...string) { + w.Header().Set("Allow", strings.Join(m, ",")) + http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) +} diff --git a/etcdserver/etcdhttp/http_test.go b/etcdserver/etcdhttp/http_test.go index 93d871ba5..df778e37a 100644 --- a/etcdserver/etcdhttp/http_test.go +++ b/etcdserver/etcdhttp/http_test.go @@ -363,7 +363,7 @@ func TestV2MachinesEndpoint(t *testing.T) { defer s.Close() for _, tt := range tests { - req, _ := http.NewRequest(tt.method, s.URL + machinesPrefix, nil) + req, _ := http.NewRequest(tt.method, s.URL+machinesPrefix, nil) resp, err := http.DefaultClient.Do(req) if err != nil { t.Fatal(err) @@ -385,7 +385,7 @@ func TestServeMachines(t *testing.T) { h.serveMachines(writer, req) w := "http://localhost:8080, http://localhost:8081, http://localhost:8082" if g := writer.Body.String(); g != w { - t.Errorf("data = %s, want %s", g, w) + t.Errorf("body = %s, want %s", g, w) } if writer.Code != http.StatusOK { t.Errorf("header = %d, want %d", writer.Code, http.StatusOK) From d5194915451bcc170c040a911a01e836fc4bef4d Mon Sep 17 00:00:00 2001 From: Yicheng Qin Date: Tue, 9 Sep 2014 15:51:28 -0700 Subject: [PATCH 4/5] http: add TODO for peers var --- etcdserver/etcdhttp/http.go | 4 +++- main.go | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/etcdserver/etcdhttp/http.go b/etcdserver/etcdhttp/http.go index ee7ae6056..52507ac6e 100644 --- a/etcdserver/etcdhttp/http.go +++ b/etcdserver/etcdhttp/http.go @@ -147,7 +147,9 @@ func httpPost(url string, data []byte) bool { type Handler struct { Timeout time.Duration Server *etcdserver.Server - Peers Peers + // TODO: dynamic configuration may make this outdated. take care of it. + // TODO: dynamic configuration may introduce race also. + Peers Peers } func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { diff --git a/main.go b/main.go index 93fcdac2a..7ed63934d 100644 --- a/main.go +++ b/main.go @@ -70,7 +70,7 @@ func main() { h := &etcdhttp.Handler{ Timeout: *timeout, Server: s, - Peers: peers, + Peers: *peers, } http.Handle("/", h) log.Fatal(http.ListenAndServe(*laddr, nil)) From 01871e7c2905290c3e2d87056b274183f56b8259 Mon Sep 17 00:00:00 2001 From: Yicheng Qin Date: Tue, 9 Sep 2014 16:20:02 -0700 Subject: [PATCH 5/5] http: use sort.Strings --- etcdserver/etcdhttp/http.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etcdserver/etcdhttp/http.go b/etcdserver/etcdhttp/http.go index 52507ac6e..fcea0dc3d 100644 --- a/etcdserver/etcdhttp/http.go +++ b/etcdserver/etcdhttp/http.go @@ -215,7 +215,7 @@ func (h Handler) serveMachines(w http.ResponseWriter, r *http.Request) { urls = append(urls, addScheme(addr)) } } - sort.Sort(sort.StringSlice(urls)) + sort.Strings(urls) w.Write([]byte(strings.Join(urls, ", "))) }