From 85095760ff0cc82978b108aaa688a3c02f15a1c2 Mon Sep 17 00:00:00 2001 From: Anthony Romano Date: Wed, 31 May 2017 11:47:34 -0700 Subject: [PATCH 1/3] e2e: test txn over grpc json --- e2e/v3_curl_test.go | 51 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/e2e/v3_curl_test.go b/e2e/v3_curl_test.go index 0f7193a8577..af137c4a744 100644 --- a/e2e/v3_curl_test.go +++ b/e2e/v3_curl_test.go @@ -20,6 +20,8 @@ import ( pb "github.com/coreos/etcd/etcdserver/etcdserverpb" "github.com/coreos/etcd/pkg/testutil" + + "github.com/grpc-ecosystem/grpc-gateway/runtime" ) func TestV3CurlPutGetNoTLS(t *testing.T) { testCurlPutGetGRPCGateway(t, &configNoTLS) } @@ -111,3 +113,52 @@ func TestV3CurlWatch(t *testing.T) { t.Fatal(err) } } + +func TestV3CurlTxn(t *testing.T) { + 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) + } + }() + + txn := &pb.TxnRequest{ + Compare: []*pb.Compare{ + { + Key: []byte("foo"), + Result: pb.Compare_EQUAL, + Target: pb.Compare_CREATE, + TargetUnion: &pb.Compare_CreateRevision{0}, + }, + }, + Success: []*pb.RequestOp{ + { + Request: &pb.RequestOp_RequestPut{ + RequestPut: &pb.PutRequest{ + Key: []byte("foo"), + Value: []byte("bar"), + }, + }, + }, + }, + } + m := &runtime.JSONPb{} + jsonDat, jerr := m.Marshal(txn) + if jerr != nil { + t.Fatal(jerr) + } + expected := `"succeeded":true,"responses":[{"response_put":{"header":{"revision":"2"}}}]` + if err = cURLPost(epc, cURLReq{endpoint: "/v3alpha/kv/txn", value: string(jsonDat), expected: expected}); err != nil { + t.Fatalf("failed txn with curl (%v)", err) + } + + // was crashing etcd server + malformed := `{"compare":[{"result":0,"target":1,"key":"Zm9v","TargetUnion":null}],"success":[{"Request":{"RequestPut":{"key":"Zm9v","value":"YmFy"}}}]}` + if err = cURLPost(epc, cURLReq{endpoint: "/v3alpha/kv/txn", value: malformed, expected: "error"}); err != nil { + t.Fatalf("failed put with curl (%v)", err) + } +} From 1467b456ae00688d2e3640cacb82c728535a1b5b Mon Sep 17 00:00:00 2001 From: Anthony Romano Date: Wed, 31 May 2017 12:00:16 -0700 Subject: [PATCH 2/3] dev-guide: add txn json example --- Documentation/dev-guide/api_grpc_gateway.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Documentation/dev-guide/api_grpc_gateway.md b/Documentation/dev-guide/api_grpc_gateway.md index 7169a7a0193..3e99f1f4e58 100644 --- a/Documentation/dev-guide/api_grpc_gateway.md +++ b/Documentation/dev-guide/api_grpc_gateway.md @@ -38,6 +38,15 @@ curl -L http://localhost:2379/v3alpha/kv/put \ # {"result":{"header":{"cluster_id":"12585971608760269493","member_id":"13847567121247652255","revision":"2","raft_term":"2"},"events":[{"kv":{"key":"Zm9v","create_revision":"2","mod_revision":"2","version":"1","value":"YmFy"}}]}} ``` +Use `curl` to issue a transaction: + +```bash +curl -L http://localhost:2379/v3alpha/kv/txn \ + -X POST \ + -d '{"compare":[{"target":"CREATE","key":"Zm9v","createRevision":"2"}],"success":[{"requestPut":{"key":"Zm9v","value":"YmFy"}}]}' +# {"header":{"cluster_id":"12585971608760269493","member_id":"13847567121247652255","revision":"3","raft_term":"2"},"succeeded":true,"responses":[{"response_put":{"header":{"revision":"3"}}}]} +``` + ## Swagger Generated [Swagger][swagger] API definitions can be found at [rpc.swagger.json][swagger-doc]. From d8210da50594d1705d5bbbdf4d08ea4422caeb36 Mon Sep 17 00:00:00 2001 From: Anthony Romano Date: Wed, 31 May 2017 12:39:52 -0700 Subject: [PATCH 3/3] v3rpc: treat nil txn request op as error Fixes #7889 --- etcdserver/api/v3rpc/key.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/etcdserver/api/v3rpc/key.go b/etcdserver/api/v3rpc/key.go index b2ae1adeeed..65901709a66 100644 --- a/etcdserver/api/v3rpc/key.go +++ b/etcdserver/api/v3rpc/key.go @@ -253,8 +253,8 @@ func checkRequestOp(u *pb.RequestOp) error { return checkDeleteRequest(uv.RequestDeleteRange) } default: - // empty op - return nil + // empty op / nil entry + return rpctypes.ErrGRPCKeyNotFound } return nil }