From 273a43d4d88ce0f9515bd73ac32f4ab7f909bc81 Mon Sep 17 00:00:00 2001 From: Gyuho Lee Date: Thu, 1 Mar 2018 12:48:56 -0800 Subject: [PATCH 1/3] api/v3election: error on missing "leader" field Signed-off-by: Gyuho Lee --- etcdserver/api/v3election/election.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/etcdserver/api/v3election/election.go b/etcdserver/api/v3election/election.go index f9061c079..d1b2767be 100644 --- a/etcdserver/api/v3election/election.go +++ b/etcdserver/api/v3election/election.go @@ -15,6 +15,8 @@ package v3election import ( + "errors" + "golang.org/x/net/context" "github.com/coreos/etcd/clientv3" @@ -22,6 +24,10 @@ import ( epb "github.com/coreos/etcd/etcdserver/api/v3election/v3electionpb" ) +// ErrMissingLeaderKey is returned when election API request +// is missing the "leader" field. +var ErrMissingLeaderKey = errors.New(`"leader" field must be provided`) + type electionServer struct { c *clientv3.Client } @@ -51,6 +57,9 @@ func (es *electionServer) Campaign(ctx context.Context, req *epb.CampaignRequest } func (es *electionServer) Proclaim(ctx context.Context, req *epb.ProclaimRequest) (*epb.ProclaimResponse, error) { + if req.Leader == nil { + return nil, ErrMissingLeaderKey + } s, err := es.session(ctx, req.Leader.Lease) if err != nil { return nil, err @@ -98,6 +107,9 @@ func (es *electionServer) Leader(ctx context.Context, req *epb.LeaderRequest) (* } func (es *electionServer) Resign(ctx context.Context, req *epb.ResignRequest) (*epb.ResignResponse, error) { + if req.Leader == nil { + return nil, ErrMissingLeaderKey + } s, err := es.session(ctx, req.Leader.Lease) if err != nil { return nil, err From 4e7b9d223dc70f546de741ee17181a93d86fa413 Mon Sep 17 00:00:00 2001 From: Gyuho Lee Date: Thu, 1 Mar 2018 14:30:42 -0800 Subject: [PATCH 2/3] e2e: add "Election" grpc-gateway test cases Signed-off-by: Gyuho Lee --- e2e/v3_curl_test.go | 116 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) diff --git a/e2e/v3_curl_test.go b/e2e/v3_curl_test.go index af137c4a7..30cdd647c 100644 --- a/e2e/v3_curl_test.go +++ b/e2e/v3_curl_test.go @@ -15,9 +15,13 @@ package e2e import ( + "encoding/base64" "encoding/json" + "path" + "strconv" "testing" + epb "github.com/coreos/etcd/etcdserver/api/v3election/v3electionpb" pb "github.com/coreos/etcd/etcdserver/etcdserverpb" "github.com/coreos/etcd/pkg/testutil" @@ -162,3 +166,115 @@ func TestV3CurlTxn(t *testing.T) { t.Fatalf("failed put with curl (%v)", err) } } + +func TestV3CurlCampaignNoTLS(t *testing.T) { + for _, p := range apiPrefix { + testCtl(t, testV3CurlCampaign, withApiPrefix(p), withCfg(configNoTLS)) + } +} + +func testV3CurlCampaign(cx ctlCtx) { + cdata, err := json.Marshal(&epb.CampaignRequest{ + Name: []byte("/election-prefix"), + Value: []byte("v1"), + }) + if err != nil { + cx.t.Fatal(err) + } + cargs := cURLPrefixArgs(cx.epc, "POST", cURLReq{ + endpoint: path.Join(cx.apiPrefix, "/election/campaign"), + value: string(cdata), + }) + lines, err := spawnWithExpectLines(cargs, `"leader":{"name":"`) + if err != nil { + cx.t.Fatalf("failed post campaign request (%s) (%v)", cx.apiPrefix, err) + } + if len(lines) != 1 { + cx.t.Fatalf("len(lines) expected 1, got %+v", lines) + } + + var cresp campaignResponse + if err = json.Unmarshal([]byte(lines[0]), &cresp); err != nil { + cx.t.Fatalf("failed to unmarshal campaign response %v", err) + } + ndata, err := base64.StdEncoding.DecodeString(cresp.Leader.Name) + if err != nil { + cx.t.Fatalf("failed to decode leader key %v", err) + } + kdata, err := base64.StdEncoding.DecodeString(cresp.Leader.Key) + if err != nil { + cx.t.Fatalf("failed to decode leader key %v", err) + } + + rev, _ := strconv.ParseInt(cresp.Leader.Rev, 10, 64) + lease, _ := strconv.ParseInt(cresp.Leader.Lease, 10, 64) + pdata, err := json.Marshal(&epb.ProclaimRequest{ + Leader: &epb.LeaderKey{ + Name: ndata, + Key: kdata, + Rev: rev, + Lease: lease, + }, + Value: []byte("v2"), + }) + if err != nil { + cx.t.Fatal(err) + } + if err = cURLPost(cx.epc, cURLReq{ + endpoint: path.Join(cx.apiPrefix, "/election/proclaim"), + value: string(pdata), + expected: `"revision":`, + }); err != nil { + cx.t.Fatalf("failed post proclaim request (%s) (%v)", cx.apiPrefix, err) + } +} + +func TestV3CurlProclaimMissiongLeaderKeyNoTLS(t *testing.T) { + for _, p := range apiPrefix { + testCtl(t, testV3CurlProclaimMissiongLeaderKey, withApiPrefix(p), withCfg(configNoTLS)) + } +} + +func testV3CurlProclaimMissiongLeaderKey(cx ctlCtx) { + pdata, err := json.Marshal(&epb.ProclaimRequest{Value: []byte("v2")}) + if err != nil { + cx.t.Fatal(err) + } + if err != nil { + cx.t.Fatal(err) + } + if err = cURLPost(cx.epc, cURLReq{ + endpoint: path.Join(cx.apiPrefix, "/election/proclaim"), + value: string(pdata), + expected: `{"error":"\"leader\" field must be provided","code":2}`, + }); err != nil { + cx.t.Fatalf("failed post proclaim request (%s) (%v)", cx.apiPrefix, err) + } +} + +func TestV3CurlResignMissiongLeaderKeyNoTLS(t *testing.T) { + for _, p := range apiPrefix { + testCtl(t, testV3CurlResignMissiongLeaderKey, withApiPrefix(p), withCfg(configNoTLS)) + } +} + +func testV3CurlResignMissiongLeaderKey(cx ctlCtx) { + if err := cURLPost(cx.epc, cURLReq{ + endpoint: path.Join(cx.apiPrefix, "/election/resign"), + value: `{}`, + expected: `{"error":"\"leader\" field must be provided","code":2}`, + }); err != nil { + cx.t.Fatalf("failed post resign request (%s) (%v)", cx.apiPrefix, err) + } +} + +// to manually decode; JSON marshals integer fields with +// string types, so can't unmarshal with epb.CampaignResponse +type campaignResponse struct { + Leader struct { + Name string `json:"name,omitempty"` + Key string `json:"key,omitempty"` + Rev string `json:"rev,omitempty"` + Lease string `json:"lease,omitempty"` + } `json:"leader,omitempty"` +} From 3bb8edc6aaa3ac5e054dc763aa730f6305353e82 Mon Sep 17 00:00:00 2001 From: Gyuho Lee Date: Wed, 7 Mar 2018 00:03:02 -0800 Subject: [PATCH 3/3] e2e: fix missing "apiPrefix" Signed-off-by: Gyuho Lee --- e2e/v3_curl_test.go | 141 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 115 insertions(+), 26 deletions(-) diff --git a/e2e/v3_curl_test.go b/e2e/v3_curl_test.go index 30cdd647c..87ae535fa 100644 --- a/e2e/v3_curl_test.go +++ b/e2e/v3_curl_test.go @@ -167,43 +167,136 @@ func TestV3CurlTxn(t *testing.T) { } } -func TestV3CurlCampaignNoTLS(t *testing.T) { - for _, p := range apiPrefix { - testCtl(t, testV3CurlCampaign, withApiPrefix(p), withCfg(configNoTLS)) +func TestV3CurlAuthAlpha(t *testing.T) { testV3CurlAuth(t, "/v3alpha") } +func TestV3CurlAuthBeta(t *testing.T) { testV3CurlAuth(t, "/v3beta") } +func testV3CurlAuth(t *testing.T, pathPrefix string) { + defer testutil.AfterTest(t) + epc, err := newEtcdProcessCluster(&configNoTLS) + if err != nil { + t.Fatalf("could not start etcd process cluster (%v)", err) + } + defer func() { + if cerr := epc.Close(); err != nil { + t.Fatalf("error closing etcd processes (%v)", cerr) + } + }() + + // create root user + userreq, err := json.Marshal(&pb.AuthUserAddRequest{Name: string("root"), Password: string("toor")}) + testutil.AssertNil(t, err) + + if err = cURLPost(epc, cURLReq{endpoint: path.Join(pathPrefix, "/auth/user/add"), value: string(userreq), expected: "revision"}); err != nil { + t.Fatalf("failed add user with curl (%v)", err) + } + + // create root role + rolereq, err := json.Marshal(&pb.AuthRoleAddRequest{Name: string("root")}) + testutil.AssertNil(t, err) + + if err = cURLPost(epc, cURLReq{endpoint: path.Join(pathPrefix, "/auth/role/add"), value: string(rolereq), expected: "revision"}); err != nil { + t.Fatalf("failed create role with curl (%v)", err) + } + + // grant root role + grantrolereq, err := json.Marshal(&pb.AuthUserGrantRoleRequest{User: string("root"), Role: string("root")}) + testutil.AssertNil(t, err) + + if err = cURLPost(epc, cURLReq{endpoint: path.Join(pathPrefix, "/auth/user/grant"), value: string(grantrolereq), expected: "revision"}); err != nil { + t.Fatalf("failed grant role with curl (%v)", err) + } + + // enable auth + if err = cURLPost(epc, cURLReq{endpoint: path.Join(pathPrefix, "/auth/enable"), value: string("{}"), expected: "revision"}); err != nil { + t.Fatalf("failed enable auth with curl (%v)", err) + } + + // put "bar" into "foo" + putreq, err := json.Marshal(&pb.PutRequest{Key: []byte("foo"), Value: []byte("bar")}) + testutil.AssertNil(t, err) + + // fail put no auth + if err = cURLPost(epc, cURLReq{endpoint: path.Join(pathPrefix, "/kv/put"), value: string(putreq), expected: "error"}); err != nil { + t.Fatalf("failed no auth put with curl (%v)", err) + } + + // auth request + authreq, err := json.Marshal(&pb.AuthenticateRequest{Name: string("root"), Password: string("toor")}) + testutil.AssertNil(t, err) + + var ( + authHeader string + cmdArgs []string + lineFunc = func(txt string) bool { return true } + ) + + cmdArgs = cURLPrefixArgs(epc, "POST", cURLReq{endpoint: path.Join(pathPrefix, "/auth/authenticate"), value: string(authreq)}) + proc, err := spawnCmd(cmdArgs) + testutil.AssertNil(t, err) + + cURLRes, err := proc.ExpectFunc(lineFunc) + testutil.AssertNil(t, err) + + authRes := make(map[string]interface{}) + testutil.AssertNil(t, json.Unmarshal([]byte(cURLRes), &authRes)) + + token, ok := authRes["token"].(string) + if !ok { + t.Fatalf("failed invalid token in authenticate response with curl") + } + + authHeader = "Authorization : " + token + + // put with auth + if err = cURLPost(epc, cURLReq{endpoint: path.Join(pathPrefix, "/kv/put"), value: string(putreq), header: authHeader, expected: "revision"}); err != nil { + t.Fatalf("failed auth put with curl (%v)", err) } } -func testV3CurlCampaign(cx ctlCtx) { +func TestV3CurlCampaignAlpha(t *testing.T) { testV3CurlCampaign(t, "/v3alpha") } +func TestV3CurlCampaignBeta(t *testing.T) { testV3CurlCampaign(t, "/v3beta") } +func testV3CurlCampaign(t *testing.T, pathPrefix string) { + defer testutil.AfterTest(t) + + epc, err := newEtcdProcessCluster(&configNoTLS) + if err != nil { + t.Fatalf("could not start etcd process cluster (%v)", err) + } + defer func() { + if cerr := epc.Close(); err != nil { + t.Fatalf("error closing etcd processes (%v)", cerr) + } + }() + cdata, err := json.Marshal(&epb.CampaignRequest{ Name: []byte("/election-prefix"), Value: []byte("v1"), }) if err != nil { - cx.t.Fatal(err) + t.Fatal(err) } - cargs := cURLPrefixArgs(cx.epc, "POST", cURLReq{ - endpoint: path.Join(cx.apiPrefix, "/election/campaign"), + cargs := cURLPrefixArgs(epc, "POST", cURLReq{ + endpoint: path.Join(pathPrefix, "/election/campaign"), value: string(cdata), }) lines, err := spawnWithExpectLines(cargs, `"leader":{"name":"`) if err != nil { - cx.t.Fatalf("failed post campaign request (%s) (%v)", cx.apiPrefix, err) + t.Fatalf("failed post campaign request (%s) (%v)", pathPrefix, err) } if len(lines) != 1 { - cx.t.Fatalf("len(lines) expected 1, got %+v", lines) + t.Fatalf("len(lines) expected 1, got %+v", lines) } var cresp campaignResponse if err = json.Unmarshal([]byte(lines[0]), &cresp); err != nil { - cx.t.Fatalf("failed to unmarshal campaign response %v", err) + t.Fatalf("failed to unmarshal campaign response %v", err) } ndata, err := base64.StdEncoding.DecodeString(cresp.Leader.Name) if err != nil { - cx.t.Fatalf("failed to decode leader key %v", err) + t.Fatalf("failed to decode leader key %v", err) } kdata, err := base64.StdEncoding.DecodeString(cresp.Leader.Key) if err != nil { - cx.t.Fatalf("failed to decode leader key %v", err) + t.Fatalf("failed to decode leader key %v", err) } rev, _ := strconv.ParseInt(cresp.Leader.Rev, 10, 64) @@ -218,21 +311,19 @@ func testV3CurlCampaign(cx ctlCtx) { Value: []byte("v2"), }) if err != nil { - cx.t.Fatal(err) + t.Fatal(err) } - if err = cURLPost(cx.epc, cURLReq{ - endpoint: path.Join(cx.apiPrefix, "/election/proclaim"), + if err = cURLPost(epc, cURLReq{ + endpoint: path.Join(pathPrefix, "/election/proclaim"), value: string(pdata), expected: `"revision":`, }); err != nil { - cx.t.Fatalf("failed post proclaim request (%s) (%v)", cx.apiPrefix, err) + t.Fatalf("failed post proclaim request (%s) (%v)", pathPrefix, err) } } func TestV3CurlProclaimMissiongLeaderKeyNoTLS(t *testing.T) { - for _, p := range apiPrefix { - testCtl(t, testV3CurlProclaimMissiongLeaderKey, withApiPrefix(p), withCfg(configNoTLS)) - } + testCtl(t, testV3CurlProclaimMissiongLeaderKey, withCfg(configNoTLS)) } func testV3CurlProclaimMissiongLeaderKey(cx ctlCtx) { @@ -244,27 +335,25 @@ func testV3CurlProclaimMissiongLeaderKey(cx ctlCtx) { cx.t.Fatal(err) } if err = cURLPost(cx.epc, cURLReq{ - endpoint: path.Join(cx.apiPrefix, "/election/proclaim"), + endpoint: path.Join("/v3beta", "/election/proclaim"), value: string(pdata), expected: `{"error":"\"leader\" field must be provided","code":2}`, }); err != nil { - cx.t.Fatalf("failed post proclaim request (%s) (%v)", cx.apiPrefix, err) + cx.t.Fatalf("failed post proclaim request (%s) (%v)", "/v3beta", err) } } func TestV3CurlResignMissiongLeaderKeyNoTLS(t *testing.T) { - for _, p := range apiPrefix { - testCtl(t, testV3CurlResignMissiongLeaderKey, withApiPrefix(p), withCfg(configNoTLS)) - } + testCtl(t, testV3CurlResignMissiongLeaderKey, withCfg(configNoTLS)) } func testV3CurlResignMissiongLeaderKey(cx ctlCtx) { if err := cURLPost(cx.epc, cURLReq{ - endpoint: path.Join(cx.apiPrefix, "/election/resign"), + endpoint: path.Join("/v3beta", "/election/resign"), value: `{}`, expected: `{"error":"\"leader\" field must be provided","code":2}`, }); err != nil { - cx.t.Fatalf("failed post resign request (%s) (%v)", cx.apiPrefix, err) + cx.t.Fatalf("failed post resign request (%s) (%v)", "/v3beta", err) } }