Browse Source

Merge pull request #1023 from unihorn/117

etcdhttp: add /v2/admin/machines endpoint
Xiang Li 11 years ago
parent
commit
d9b35470a1
3 changed files with 84 additions and 2 deletions
  1. 39 2
      etcdserver/etcdhttp/http.go
  2. 44 0
      etcdserver/etcdhttp/http_test.go
  3. 1 0
      main.go

+ 39 - 2
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,9 @@ func httpPost(url string, data []byte) bool {
 type Handler struct {
 	Timeout time.Duration
 	Server  *etcdserver.Server
+	// 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) {
@@ -156,6 +168,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 +202,23 @@ 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" {
+		allow(w, "GET", "HEAD")
+		return
+	}
+	urls := make([]string, 0)
+	for _, addrs := range h.Peers {
+		for _, addr := range addrs {
+			urls = append(urls, addScheme(addr))
+		}
+	}
+	sort.Strings(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 {
@@ -320,3 +351,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)
+}

+ 44 - 0
etcdserver/etcdhttp/http_test.go

@@ -347,3 +347,47 @@ func TestWaitForEventCancelledContext(t *testing.T) {
 		t.Fatalf("nil err returned with cancelled context!")
 	}
 }
+
+func TestV2MachinesEndpoint(t *testing.T) {
+	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()
+
+	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 != tt.wcode {
+			t.Errorf("StatusCode = %d, expected %d", resp.StatusCode, tt.wcode)
+		}
+	}
+}
+
+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()
+	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("body = %s, want %s", g, w)
+	}
+	if writer.Code != http.StatusOK {
+		t.Errorf("header = %d, want %d", writer.Code, http.StatusOK)
+	}
+}

+ 1 - 0
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))