-
Notifications
You must be signed in to change notification settings - Fork 9.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
grpc-gateway: txn marshaling broken #7889
Comments
Hi guys, if no one has started to investigate this bug, I would like to work on it. I read through the stack trace in order to get a general idea of the data flow. Correct me if I'm wrong, but I believe that the unmarshaling of transaction requests begins on line 724 of cmd/vendor/google.golang.org/grpc/server.go: If that is the case, then I think a good start to the investigation would be stepping through those areas of the code with a debugger. I actually haven't been able to determine a good way of reproducing this marshaling defect. Could you please provide steps for reproducing the defect. I have been googling for a while, but I wasn't able to find documentation explaining how to initiate a transactions to etcd. |
This is how to repro: curl -L http://localhost:2379/v3alpha/kv/txn \
-X POST \
-d '{"compare":[{"result":0,"target":1,"key":"Zm9v","TargetUnion":null}],"success":[{"Request":{"RequestPut":{"key":"Zm9v","value":"YmFy"}}}]}' |
@heyitsanthony It looks like the marshaling code is fine. I recreated : The problem actually appears to be related to an incomplete reading of the transport stream. So since we're not reading the entire stream (in our specific case we're only reading 11 bytes when we should really be reading 23 bytes), the bytes related to the Success field are missing by the time unmarshaller gets the data. From what I can tell, the recvMsg function from rpc_util.go is determining the length of bytes to read from the stream based off of the header of the gRPC message. This is just a small update on this issue, I'm actually still in the process of investigating why the header does not contain the proper length, I just thought people might appreciate an update. |
@heyitsanthony
There are actually two problems with this JSON string. The first issue is that "Request" and "RequestPut" are capitalized instead of in camel case or with underscores between the lower cased words (e.g. "request_put"). We need to adhere to this format of JSON, because library that we're using to unmarshal JSON into protobufs only searches for those specific JSON formats in the provided JSON. Here's the code segment that enforces camel casing and underscoring:
~From etcd/cmd/vendor/github.com/golang/protobuf/jsonpb/jsonpb.go The second issue is that the "Request" field should not be included in the JSON. The success part of the JSON should actually be: Since in RequestOp, "Request" is name of a field which is defined with a "oneof" property, I think that "Request" may have snuck into the JSON due to improper handling of the "oneof" property. If you want to verify that "Request" should not be included in the JSON, take a look at the pieces of code that I found in the protobuf library. The following is the definition of a message with a oneof property:
~From https://github.com/golang/protobuf/blob/master/jsonpb/jsonpb_test_proto/test_objects.proto Here is a test case using the MsgWithOneof message:
~From https://github.com/golang/protobuf/blob/master/jsonpb/jsonpb_test.go The expected JSON (i.e. My question now is what generated the following JSON that was given to me to reproduce this issue? |
It was generated using grpc-gateway's |
I figured as much. Before I go any further, I would appreciate it if you could tell me whether I have the correct high-level overview of this particular code. So according to api_grpc_gateway.md, the purpose of the grpc-gateway is to accept RESTful calls containing JSON data. Based off of this JSON support feature, I'm guessing that there must be an etcd client which takes protocol buffers and converts them into JSON, which is then sent to the grpc_gateway. In the initial issue report, you state the following:
So if I am understanding you correctly, then you're saying that some protocol buffer was fed into the grpc_gateway I'm confused about how "If(CreateRev("foo")=0) { Put("foo", "bar") }" is transformed into the JSON string. I'm sure I would be less confused, if I could find the code that deals with this transformation. As a start, it would be great if you could explain the origin of "If(CreateRev("foo")=0) { Put("foo", "bar") }". Is it pseudo code? Is it a command inputed into some executable client program? It would also be helpful if you could let me know where you got the string version of the JSON in the first place. The filename or function where you put the print statement to get that JSON would be perfect. With those two bits of information, I'll probably be in good shape to implement a fix to this issue. |
in txn := &etcdserverpb.TxnRequest{
Compare: []*etcdserverpb.Compare{
&etcdserverpb.Compare{
Key: []byte("foo"),
Result: etcdserverpb.Compare_EQUAL,
Target: etcdserverpb.Compare_CREATE,
TargetUnion: &etcdserverpb.Compare_CreateRevision{0},
},
},
Success: []*etcdserverpb.RequestOp{
{
Request: &etcdserverpb.RequestOp_RequestPut{
RequestPut: &etcdserverpb.PutRequest{
Key: []byte("foo"),
Value: []byte("bar"),
},
},
},
},
}
fmt.Printf("start: %+v\n", txn)
b, _ := marshaler.Marshal(txn)
fmt.Println("json: ", string(b))
txn = &etcdserverpb.TxnRequest{}
marshaler.Unmarshal(b, txn)
fmt.Printf("unjson: %+v\n", txn) which gives:
which is from with origName = false, I get: {"compare":[{"target":"CREATE","key":"Zm9v","createRevision":"0"}],"success":[{"requestPut":{"key":"Zm9v","value":"YmFy"}}]} which parses OK and gives the resposne: {"header":{"cluster_id":"14841639068965178418","member_id":"10276657743932975437","revision":"2","raft_term":"2"},"succeeded":true,"responses":[{"response_put":{"header":{"revision":"2"}}}]} I'll add some tests for this and close out this issue since marshaling is fine, just that the gateway seemingly can't unmarshal what it marshals. The crashing is still a problem, though. |
Thanks for the detailed explanation, but the question remains as to where the JSON that you gave me to reproduce the issue came from. None of the JSON you printed out in the previous post matches the following JSON.
This JSON contains has the extra "Request" field defined and when this extra piece of JSON messes up the unmarshaling of the success protocol buffer structure, then the program crashes due to the nil success structure. So I don't understand why we would close this issue without figuring out what generated that JSON and fixing it. |
@mangoslicer it's from |
Alright that solves that mystery. It also looks like the JSONBuiltin is throwing a marshalling error as well as producing that faulty JSON. So are any of the etcd clients which talk to the grpc_gateway using the JSONBuiltin marshaler? If so, we probably want to switch over to a different marshaler. |
What is is interesting is using 3.2.0 I will pass this through the grpc-gateway and it seems to randomly fail.
vs your example
https://travis-ci.org/hexfusion/perl-net-etcd/builds/243610166 I guess the fact that it fails is one thing but what is your theory on the randomness. In the travis tests it is checking against different versions of Perl. But if you refreshed the tests it will fail in a different order. I don't see this is master that I can reproduce. But I did find the randomness odd. |
I try a txn with If(CreateRev("foo")=0) { Put("foo", "bar") }, generated with the gateway's marshaler from the pb struct:
'{"compare":[{"result":0,"target":1,"key":"Zm9v","TargetUnion":null}],"success":[{"Request":{"RequestPut":{"key":"Zm9v","value":"YmFy"}}}]}'
The %+v output for the pb structure gives:
However, the gateway decodes it into the pb structure:
Then crashes out before returning from
client.Txn
:The text was updated successfully, but these errors were encountered: