Browse Source

Merge pull request #1493 from bcwaldon/etcdctl-members

etcdctl members [list|add|remove]
Brian Waldon 11 years ago
parent
commit
95231c1278
3 changed files with 161 additions and 11 deletions
  1. 57 11
      client/members.go
  2. 102 0
      etcdctl/command/member_commands.go
  3. 2 0
      etcdctl/main.go

+ 57 - 11
client/members.go

@@ -17,6 +17,7 @@
 package client
 
 import (
+	"bytes"
 	"encoding/json"
 	"fmt"
 	"net/http"
@@ -54,6 +55,8 @@ func NewMembersAPI(tr *http.Transport, ep string, to time.Duration) (MembersAPI,
 
 type MembersAPI interface {
 	List() ([]httptypes.Member, error)
+	Add(peerURL string) (*httptypes.Member, error)
+	Remove(mID string) error
 }
 
 type httpMembersAPI struct {
@@ -66,11 +69,7 @@ func (m *httpMembersAPI) List() ([]httptypes.Member, error) {
 		return nil, err
 	}
 
-	mResponse := httpMembersAPIResponse{
-		code: httpresp.StatusCode,
-	}
-
-	if err := mResponse.err(); err != nil {
+	if err := assertStatusCode(http.StatusOK, httpresp.StatusCode); err != nil {
 		return nil, err
 	}
 
@@ -82,15 +81,33 @@ func (m *httpMembersAPI) List() ([]httptypes.Member, error) {
 	return []httptypes.Member(mCollection), nil
 }
 
-type httpMembersAPIResponse struct {
-	code int
+func (m *httpMembersAPI) Add(peerURL string) (*httptypes.Member, error) {
+	req := &membersAPIActionAdd{peerURL: peerURL}
+	httpresp, body, err := m.client.doWithTimeout(req)
+	if err != nil {
+		return nil, err
+	}
+
+	if err := assertStatusCode(http.StatusCreated, httpresp.StatusCode); err != nil {
+		return nil, err
+	}
+
+	var memb httptypes.Member
+	if err := json.Unmarshal(body, &memb); err != nil {
+		return nil, err
+	}
+
+	return &memb, nil
 }
 
-func (r *httpMembersAPIResponse) err() (err error) {
-	if r.code != http.StatusOK {
-		err = fmt.Errorf("unrecognized status code %d", r.code)
+func (m *httpMembersAPI) Remove(memberID string) error {
+	req := &membersAPIActionRemove{memberID: memberID}
+	httpresp, _, err := m.client.doWithTimeout(req)
+	if err != nil {
+		return err
 	}
-	return
+
+	return assertStatusCode(http.StatusNoContent, httpresp.StatusCode)
 }
 
 type membersAPIActionList struct{}
@@ -99,3 +116,32 @@ func (l *membersAPIActionList) httpRequest(ep url.URL) *http.Request {
 	req, _ := http.NewRequest("GET", ep.String(), nil)
 	return req
 }
+
+type membersAPIActionRemove struct {
+	memberID string
+}
+
+func (d *membersAPIActionRemove) httpRequest(ep url.URL) *http.Request {
+	ep.Path = path.Join(ep.Path, d.memberID)
+	req, _ := http.NewRequest("DELETE", ep.String(), nil)
+	return req
+}
+
+type membersAPIActionAdd struct {
+	peerURL string
+}
+
+func (a *membersAPIActionAdd) httpRequest(ep url.URL) *http.Request {
+	m := httptypes.Member{PeerURLs: []string{a.peerURL}}
+	b, _ := json.Marshal(&m)
+	req, _ := http.NewRequest("POST", ep.String(), bytes.NewReader(b))
+	req.Header.Set("Content-Type", "application/json")
+	return req
+}
+
+func assertStatusCode(want, got int) (err error) {
+	if want != got {
+		err = fmt.Errorf("unexpected status code %d", got)
+	}
+	return err
+}

+ 102 - 0
etcdctl/command/member_commands.go

@@ -0,0 +1,102 @@
+package command
+
+import (
+	"fmt"
+	"net/http"
+	"os"
+	"strings"
+
+	"github.com/coreos/etcd/Godeps/_workspace/src/github.com/codegangsta/cli"
+	"github.com/coreos/etcd/client"
+)
+
+func NewMemberCommand() cli.Command {
+	return cli.Command{
+		Name: "member",
+		Subcommands: []cli.Command{
+			cli.Command{
+				Name:   "list",
+				Usage:  "enumerate existing cluster members",
+				Action: actionMemberList,
+			},
+			cli.Command{
+				Name:   "add",
+				Usage:  "add a new member to the etcd cluster",
+				Action: actionMemberAdd,
+			},
+			cli.Command{
+				Name:   "remove",
+				Usage:  "remove an existing member from the etcd cluster",
+				Action: actionMemberRemove,
+			},
+		},
+	}
+}
+
+func actionMemberList(c *cli.Context) {
+	if len(c.Args()) != 0 {
+		fmt.Fprintln(os.Stderr, "No arguments accepted")
+		os.Exit(1)
+	}
+
+	mAPI, err := client.NewMembersAPI(&http.Transport{}, "http://127.0.0.1:4001", client.DefaultRequestTimeout)
+	if err != nil {
+		fmt.Fprintln(os.Stderr, err.Error())
+		os.Exit(1)
+	}
+
+	members, err := mAPI.List()
+	if err != nil {
+		fmt.Fprintln(os.Stderr, err.Error())
+		os.Exit(1)
+	}
+
+	for _, m := range members {
+		fmt.Printf("%s: name=%s peerURLs=%s clientURLs=%s\n", m.ID, m.Name, strings.Join(m.PeerURLs, ","), strings.Join(m.ClientURLs, ","))
+	}
+}
+
+func actionMemberAdd(c *cli.Context) {
+	args := c.Args()
+	if len(args) != 1 {
+		fmt.Fprintln(os.Stderr, "Provide a single member peerURL")
+		os.Exit(1)
+	}
+
+	mAPI, err := client.NewMembersAPI(&http.Transport{}, "http://127.0.0.1:4001", client.DefaultRequestTimeout)
+	if err != nil {
+		fmt.Fprintln(os.Stderr, err.Error())
+		os.Exit(1)
+	}
+
+	url := args[0]
+	m, err := mAPI.Add(url)
+	if err != nil {
+		fmt.Fprintln(os.Stderr, err.Error())
+		os.Exit(1)
+	}
+
+	fmt.Printf("Added member to cluster with ID %s", m.ID)
+}
+
+func actionMemberRemove(c *cli.Context) {
+	args := c.Args()
+	if len(args) != 1 {
+		fmt.Fprintln(os.Stderr, "Provide a single member ID")
+		os.Exit(1)
+	}
+
+	mAPI, err := client.NewMembersAPI(&http.Transport{}, "http://127.0.0.1:4001", client.DefaultRequestTimeout)
+	if err != nil {
+		fmt.Fprintln(os.Stderr, err.Error())
+		os.Exit(1)
+	}
+
+	mID := args[0]
+	if err := mAPI.Remove(mID); err != nil {
+		fmt.Fprintln(os.Stderr, err.Error())
+		os.Exit(1)
+	}
+
+	fmt.Printf("Removed member %s from cluster\n", mID)
+}

+ 2 - 0
etcdctl/main.go

@@ -33,6 +33,8 @@ func main() {
 		command.NewUpdateDirCommand(),
 		command.NewWatchCommand(),
 		command.NewExecWatchCommand(),
+		command.NewMemberCommand(),
 	}
+
 	app.Run(os.Args)
 }