Skip to content
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

Zipkin json encoding via HTTP #334

Closed
wants to merge 8 commits into from

Conversation

pavolloffay
Copy link
Member

@pavolloffay pavolloffay commented Aug 15, 2017

@coveralls
Copy link

Coverage Status

Coverage remained the same at 100.0% when pulling a152d1d on pavolloffay:zipkin-json into db9f152 on uber:master.

@pavolloffay
Copy link
Member Author

How are we going to write integration tests for this? zipkin-go-opentracing supports only thrift. We could use brave but it's in java. I think we have two options:

  1. modify xdock-java to use brave
  2. crate new xdock-zipkin which would verify zipkin related stuff.

Copy link
Member

@yurishkuro yurishkuro left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't do a completely thorough review, had a few starter questions. Overall direction looks good.

cc @black-adder

if err != nil {
http.Error(w, fmt.Sprintf(app.UnableToReadBodyErrFormat, err), http.StatusBadRequest)
return
}
} else {
http.Error(w, "Only Content-Type:application/x-thrift is supported at the moment", http.StatusBadRequest)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extend err msg

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment is odd, but why custom error? I don't see a value for it

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant err msg says only x-thrift is supported, not json, so we can extend it to mention both

@@ -148,7 +187,7 @@ func TestGzipBadBody(t *testing.T) {
func TestUnsupportedContentType(t *testing.T) {
server, _ := initializeTestServer(nil)
defer server.Close()
statusCode, _, err := postBytes(server.URL+`/api/v1/spans`, []byte{}, createHeader("application/json"))
statusCode, _, err := postBytes(server.URL+`/api/v1/spans`, []byte{}, createHeader("text/html"))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

xml would be funnier

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

video/mp4 :P

type binaryAnnotation struct {
Endpoint endpoint `json:"endpoint"`
Key string `json:"key"`
Value string `json:"value"`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why did Adrian refer to oneOf? Is this the right JSON format?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think oneOf refers to bool, string num so on. In the current https://github.com/openzipkin/zipkin-api/blob/master/zipkin-api.yaml it's defined as string

}

for _, span := range spans {
if len(span.TraceID) != 16 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how are we dealing with 128bit IDs? Does Zipkin JSON encode them as one string, or hi/lo?

Copy link
Member Author

@pavolloffay pavolloffay Aug 15, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently parsing only traceId[16:] (https://github.com/openzipkin/zipkin/blob/cf4df43952f2b82496c2e4c730765e7b70eba22b/zipkin/src/main/java/zipkin/internal/Util.java#L105). Once new thrift is updated traceIdHigh will be set with [:16]

if len(span.ID) != 16 && len(span.ID) != 32 {
return nil, errSpanID
}
if len(span.ParentID) > 0 && len(span.ParentID) != 16 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I never understood this restriction in Zipkin to have the exact length - what if some leading zeroes were dropped for whatever reason, we can still parse the hex string. Especially for parsing out-of-band span data, I am not sure why we need to be this strict.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Honestly, I don't know, it's defined in api definition.

@adriancole hi, would you give us some insights here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yurishkuro updated to align with your comment. It seems that zipkin sever does the same.

}, nil
}

func parseIpv4(str string) (int32, error) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so we're only parsing IPv4 from Zipkin, not IPv6?

Note that our zipkin.thrift (in jaeger-idl) is probably behind the official Zipkin IDL, e.g. we don't have IPv6 or TraceIdHigh fields.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, the reason is that our zipkin.thrift does not support ipv6. I will leave TODO comment there.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, we should sync it up with official IDL (should be backwards compatible) - #335

@yurishkuro
Copy link
Member

yurishkuro commented Aug 15, 2017

How are we going to write integration tests for this?

I think we might be able to use the same xdock driver, but a different "client" that's implemented with Zipkin's JSON and Thrift official senders (two clients, really)

@black-adder - can we come up with a unified end-to-end testing framework? Ultimately it boils down to two parts

  1. running some configuration of the backend (could be all-in-one, or docker-compose, or k8s)
  2. having the driver hit some sort of client that can report spans to the backend (either via agent or via direct POST)

@coveralls
Copy link

Coverage Status

Coverage remained the same at 100.0% when pulling 990f499 on pavolloffay:zipkin-json into 79fba12 on uber:master.

@coveralls
Copy link

Coverage Status

Coverage remained the same at 100.0% when pulling 6a83968 on pavolloffay:zipkin-json into 79fba12 on uber:master.

}

var (
errIstUnsignedLog = errors.New("id is not an unsigned long")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: s/ist/is

@@ -122,6 +122,45 @@ func TestThriftFormat(t *testing.T) {
assert.EqualValues(t, "Cannot submit Zipkin batch: Bad times ahead\n", resBodyStr)
}

func TestJsonFormat(t *testing.T) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: could we use a fixture for this test instead which defines the test cases and then run the cases at once?

} else {
http.Error(w, "Only Content-Type:application/x-thrift is supported at the moment", http.StatusBadRequest)
http.Error(w, "Not supported Content-Type", http.StatusBadRequest)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: s/Not Supported/Unsupported/ we can literally save bytes

@@ -0,0 +1,234 @@
// Copyright (c) 2017 Uber Technologies, Inc.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

file can be json.go since it's already namespaced under zipkin.

}

func decode(body []byte) ([]zipkinSpan, error) {
type zipkinSpans []zipkinSpan
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this intermediate type definition necessary?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it can be defined in global scope, is that better?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sgtm

return tSpan, nil
}

func endpointToThrift(endp endpoint) (*zipkincore.Endpoint, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

go prefers incomprehensible variable names for short functions: https://talks.golang.org/2014/names.slide#7

so here s/endp/e

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

whaaa?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

incomprehensible 😃 then I will rename all local vars to i

import (
"encoding/json"
"fmt"
"github.com/stretchr/testify/assert"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

import ordering

assert.Equal(t, "foo", endp.ServiceName)
assert.Equal(t, "127.0.0.1", endp.IPv4)
assert.Equal(t, "2001:db8::c001", endp.IPv6)
// only service name
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this test and a couple of other tests might be slightly more readable with fixtures

@black-adder
Copy link
Contributor

  1. running some configuration of the backend (could be all-in-one, or docker-compose, or k8s)
  2. having the driver hit some sort of client that can report spans to the backend (either via agent or via direct POST)

This can be easily done with in the all-in-one integration test, however doing it via crossdock tests does seem more semantically correct. The all-in-one integration test is supposed to be a simple test that ensures the pipeline works, I don't want to conflate the test with zipkin testing logic.

Creating a test zipkin client which sends http and thrift spans to the collectors and making this run with the xdock tests seems the best approach. But I'm not entirely sure how to get this to work without creating another docker image just for the zipkin client, I'll have to experiment a bit.

@yurishkuro
Copy link
Member

@coveralls
Copy link

Coverage Status

Coverage remained the same at 100.0% when pulling 2ada3b2 on pavolloffay:zipkin-json into 9bfab15 on uber:master.

@coveralls
Copy link

Coverage Status

Coverage remained the same at 100.0% when pulling eafd168 on pavolloffay:zipkin-json into 9bfab15 on uber:master.

@coveralls
Copy link

Coverage Status

Coverage remained the same at 100.0% when pulling 1879f5e on pavolloffay:zipkin-json into 9bfab15 on uber:master.

@coveralls
Copy link

Coverage Status

Coverage remained the same at 100.0% when pulling cce519b on pavolloffay:zipkin-json into 5e5a361 on uber:master.

@coveralls
Copy link

Coverage Status

Coverage remained the same at 100.0% when pulling 2603165 on pavolloffay:zipkin-json into 5e5a361 on uber:master.

@coveralls
Copy link

Coverage Status

Coverage remained the same at 100.0% when pulling 64499c2 on pavolloffay:zipkin-json into 5e5a361 on uber:master.

@coveralls
Copy link

Coverage Status

Coverage remained the same at 100.0% when pulling 2b406ae on pavolloffay:zipkin-json into 5e5a361 on uber:master.

@pavolloffay
Copy link
Member Author

This is updated and should be ready for re-review.

expected string
statusCode int
}{
{spanJSON, "Cannot submit Zipkin batch: Bad times ahead\n", http.StatusInternalServerError},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please explicitly define the variable names in the struct: ie {payload: spanJSON, etc.}

we've been bad about this in the past but should do it for everything going forward

}

var result uint64
for i := start; i < len; i++ {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we use official the builtin golang functions for this: https://play.golang.org/p/0GUjRvy0kw or would it not work?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it produces different numbers

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not use model functions like model/TraceIDFromString?

https://github.com/uber/jaeger/blob/master/model/span.go#L146

It automatically takes care of high/low.

}, nil
}

func parseIpv4(str string) (int32, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ParseIP returns IP, which is an alias to []byte. If nil return 0 else pack bytes into long

@coveralls
Copy link

Coverage Status

Coverage remained the same at 100.0% when pulling 3211e3d on pavolloffay:zipkin-json into 5e5a361 on uber:master.

} else {
http.Error(w, "Only Content-Type:application/x-thrift is supported at the moment", http.StatusBadRequest)
http.Error(w, "Unsupported Content-Type", http.StatusBadRequest)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing return?

var tSpans []*zipkincore.Span
if contentType == "application/x-thrift" {
tSpans, err = deserializeThrift(bodyBytes)
if err != nil {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can do this outside of the if contentType==

expected string
statusCode int
}{
{payload: spanJSON, expected: "Cannot submit Zipkin batch: Bad times ahead\n", statusCode: http.StatusInternalServerError},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this is more readable

{
    payload: spanJSON, 
    expected: "Cannot submit Zipkin batch: Bad times ahead\n", 
    statusCode: http.StatusInternalServerError,
},

Annotations []annotation `json:"annotations"`
BinaryAnnotations []binaryAnnotation `json:"binaryAnnotations"`
}
type zipkinSpans []zipkinSpan
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

only used once, better to inline: var spans []zipkinSpan

Copy link
Member Author

@pavolloffay pavolloffay Aug 19, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's fine to define it here as long as it's used in multiple places (which looks like it can). Every reference to []zipkinSpan can be replaced with zipkinSpans. I count at least 3 instances where this happens.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer to use []zipkinSpan directly, less obfuscation. The type alias is useful when you add extra behavior to it, like we do with []model.Tag (sorting, etc.)

}, nil
}

func parseIpv4(str string) (int32, error) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ParseIP returns IP, which is an alias to []byte. If nil return 0 else pack bytes into long

}

var result uint64
for i := start; i < len; i++ {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not use model functions like model/TraceIDFromString?

https://github.com/uber/jaeger/blob/master/model/span.go#L146

It automatically takes care of high/low.

@pavolloffay
Copy link
Member Author

@yurishkuro I am on PTO until 28th. I don't want to block other people.. If there are no serious blockers I can submit fix PR to address latest comments:

  • IP parsing
  • reuse functions from model to handle 128 bit trace ids
  • minor improvements in http handler

Once this is in #342 could be merged (if it's ok).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants