Skip to content

Commit 29d0291

Browse files
authored
[v8] Add B3 Trace headers (#3472)
* Add B3 trace IDs to cf cli commands * Auto set span headers for each request
1 parent 529a3e1 commit 29d0291

File tree

17 files changed

+572
-0
lines changed

17 files changed

+572
-0
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package wrapper
2+
3+
import (
4+
"code.cloudfoundry.org/cli/api/cloudcontroller"
5+
"code.cloudfoundry.org/cli/api/shared"
6+
)
7+
8+
// CCTraceHeaderRequest is a wrapper that adds b3 trace headers to requests.
9+
type CCTraceHeaderRequest struct {
10+
headers *shared.TraceHeaders
11+
connection cloudcontroller.Connection
12+
}
13+
14+
// NewCCTraceHeaderRequest returns a pointer to a CCTraceHeaderRequest wrapper.
15+
func NewCCTraceHeaderRequest(trace string) *CCTraceHeaderRequest {
16+
return &CCTraceHeaderRequest{
17+
headers: shared.NewTraceHeaders(trace),
18+
}
19+
}
20+
21+
// Add tracing headers
22+
func (t *CCTraceHeaderRequest) Make(request *cloudcontroller.Request, passedResponse *cloudcontroller.Response) error {
23+
t.headers.SetHeaders(request.Request)
24+
return t.connection.Make(request, passedResponse)
25+
}
26+
27+
// Wrap sets the connection in the CCTraceHeaderRequest and returns itself.
28+
func (t *CCTraceHeaderRequest) Wrap(innerconnection cloudcontroller.Connection) cloudcontroller.Connection {
29+
t.connection = innerconnection
30+
return t
31+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package wrapper_test
2+
3+
import (
4+
"bytes"
5+
"net/http"
6+
7+
"code.cloudfoundry.org/cli/api/cloudcontroller"
8+
"code.cloudfoundry.org/cli/api/cloudcontroller/cloudcontrollerfakes"
9+
. "code.cloudfoundry.org/cli/api/cloudcontroller/wrapper"
10+
11+
. "github.com/onsi/ginkgo/v2"
12+
. "github.com/onsi/gomega"
13+
)
14+
15+
var _ = Describe("CCTraceHeaderRequest", func() {
16+
var (
17+
fakeConnection *cloudcontrollerfakes.FakeConnection
18+
19+
wrapper cloudcontroller.Connection
20+
21+
request *cloudcontroller.Request
22+
response *cloudcontroller.Response
23+
makeErr error
24+
25+
traceHeader string
26+
)
27+
28+
BeforeEach(func() {
29+
fakeConnection = new(cloudcontrollerfakes.FakeConnection)
30+
31+
traceHeader = "trace-id"
32+
33+
wrapper = NewCCTraceHeaderRequest(traceHeader).Wrap(fakeConnection)
34+
35+
body := bytes.NewReader([]byte("foo"))
36+
37+
req, err := http.NewRequest(http.MethodGet, "https://foo.bar.com/banana", body)
38+
Expect(err).NotTo(HaveOccurred())
39+
40+
response = &cloudcontroller.Response{
41+
RawResponse: []byte("some-response-body"),
42+
HTTPResponse: &http.Response{},
43+
}
44+
request = cloudcontroller.NewRequest(req, body)
45+
})
46+
47+
JustBeforeEach(func() {
48+
makeErr = wrapper.Make(request, response)
49+
})
50+
51+
Describe("Make", func() {
52+
It("Adds the request headers", func() {
53+
Expect(makeErr).NotTo(HaveOccurred())
54+
Expect(request.Header.Get("X-B3-TraceId")).To(Equal(traceHeader))
55+
Expect(request.Header.Get("X-B3-SpanId")).ToNot(BeEmpty())
56+
})
57+
58+
It("Calls the inner connection", func() {
59+
Expect(fakeConnection.MakeCallCount()).To(Equal(1))
60+
req, resp := fakeConnection.MakeArgsForCall(0)
61+
Expect(req).To(Equal(request))
62+
Expect(resp).To(Equal(response))
63+
})
64+
})
65+
})
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package wrapper
2+
3+
import (
4+
"code.cloudfoundry.org/cli/api/router"
5+
"code.cloudfoundry.org/cli/api/shared"
6+
)
7+
8+
// RoutingTraceHeaderRequest is a wrapper that adds b3 trace headers to requests.
9+
type RoutingTraceHeaderRequest struct {
10+
headers *shared.TraceHeaders
11+
connection router.Connection
12+
}
13+
14+
// NewRoutingTraceHeaderRequest returns a pointer to a RoutingTraceHeaderRequest wrapper.
15+
func NewRoutingTraceHeaderRequest(trace string) *RoutingTraceHeaderRequest {
16+
return &RoutingTraceHeaderRequest{
17+
headers: shared.NewTraceHeaders(trace),
18+
}
19+
}
20+
21+
// Add tracing headers
22+
func (t *RoutingTraceHeaderRequest) Make(request *router.Request, passedResponse *router.Response) error {
23+
t.headers.SetHeaders(request.Request)
24+
return t.connection.Make(request, passedResponse)
25+
}
26+
27+
// Wrap sets the connection in the RoutingTraceHeaderRequest and returns itself.
28+
func (t *RoutingTraceHeaderRequest) Wrap(innerconnection router.Connection) router.Connection {
29+
t.connection = innerconnection
30+
return t
31+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package wrapper_test
2+
3+
import (
4+
"bytes"
5+
"net/http"
6+
7+
"code.cloudfoundry.org/cli/api/router"
8+
"code.cloudfoundry.org/cli/api/router/routerfakes"
9+
. "code.cloudfoundry.org/cli/api/router/wrapper"
10+
11+
. "github.com/onsi/ginkgo/v2"
12+
. "github.com/onsi/gomega"
13+
)
14+
15+
var _ = Describe("CCTraceHeaderRequest", func() {
16+
var (
17+
fakeConnection *routerfakes.FakeConnection
18+
19+
wrapper router.Connection
20+
21+
request *router.Request
22+
response *router.Response
23+
makeErr error
24+
25+
traceHeader string
26+
)
27+
28+
BeforeEach(func() {
29+
fakeConnection = new(routerfakes.FakeConnection)
30+
31+
traceHeader = "trace-id"
32+
wrapper = NewRoutingTraceHeaderRequest(traceHeader).Wrap(fakeConnection)
33+
34+
body := bytes.NewReader([]byte("foo"))
35+
36+
req, err := http.NewRequest(http.MethodGet, "https://foo.bar.com/banana", body)
37+
Expect(err).NotTo(HaveOccurred())
38+
39+
response = &router.Response{
40+
RawResponse: []byte("some-response-body"),
41+
HTTPResponse: &http.Response{},
42+
}
43+
request = router.NewRequest(req, body)
44+
})
45+
46+
JustBeforeEach(func() {
47+
makeErr = wrapper.Make(request, response)
48+
})
49+
50+
Describe("Make", func() {
51+
It("Adds the request headers", func() {
52+
Expect(makeErr).NotTo(HaveOccurred())
53+
Expect(request.Header.Get("X-B3-TraceId")).To(Equal(traceHeader))
54+
Expect(request.Header.Get("X-B3-SpanId")).ToNot(BeEmpty())
55+
})
56+
57+
It("Calls the inner connection", func() {
58+
Expect(fakeConnection.MakeCallCount()).To(Equal(1))
59+
req, resp := fakeConnection.MakeArgsForCall(0)
60+
Expect(req).To(Equal(request))
61+
Expect(resp).To(Equal(response))
62+
})
63+
})
64+
})

api/shared/trace_headers.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package shared
2+
3+
import (
4+
"net/http"
5+
6+
"code.cloudfoundry.org/cli/util/trace"
7+
)
8+
9+
const (
10+
B3TraceIDHeader = "X-B3-TraceId"
11+
B3SpanIDHeader = "X-B3-SpanId"
12+
)
13+
14+
// TraceHeaders sets b3 trace headers to requests.
15+
type TraceHeaders struct {
16+
b3trace string
17+
}
18+
19+
// NewTraceHeaders returns a pointer to a TraceHeaderRequest.
20+
func NewTraceHeaders(trace string) *TraceHeaders {
21+
return &TraceHeaders{
22+
b3trace: trace,
23+
}
24+
}
25+
26+
// Add tracing headers if they are not already set.
27+
func (t *TraceHeaders) SetHeaders(request *http.Request) {
28+
// only override the trace headers if they are not already set (e.g. already explicitly set by cf curl)
29+
if request.Header.Get(B3TraceIDHeader) == "" {
30+
request.Header.Add(B3TraceIDHeader, t.b3trace)
31+
}
32+
if request.Header.Get(B3SpanIDHeader) == "" {
33+
request.Header.Add(B3SpanIDHeader, trace.GenerateRandomTraceID(16))
34+
}
35+
36+
// request.Header.Add(("B3", request.Header.Get(B3TraceIDHeader)+request.Header.Get(B3SpanIDHeader)))
37+
}

api/shared/trace_headers_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package shared_test
2+
3+
import (
4+
"net/http"
5+
6+
. "code.cloudfoundry.org/cli/api/shared"
7+
8+
. "github.com/onsi/ginkgo/v2"
9+
. "github.com/onsi/gomega"
10+
)
11+
12+
var _ = Describe("B3 Trace Headers", func() {
13+
Describe("SetHeaders", func() {
14+
Context("when there are already headers set", func() {
15+
It("does not add the headers", func() {
16+
traceHeaders := NewTraceHeaders("new_trace_id")
17+
request := &http.Request{
18+
Header: http.Header{},
19+
}
20+
request.Header.Set("X-B3-TraceId", "old_trace_id")
21+
request.Header.Set("X-B3-SpanId", "old_span_id")
22+
traceHeaders.SetHeaders(request)
23+
24+
Expect(request.Header.Get("X-B3-TraceId")).To(Equal("old_trace_id"))
25+
Expect(request.Header.Get("X-B3-SpanId")).To(Equal("old_span_id"))
26+
})
27+
})
28+
29+
Context("when there are no headers set", func() {
30+
It("adds the headers", func() {
31+
traceHeaders := NewTraceHeaders("new_trace_id")
32+
request := &http.Request{
33+
Header: http.Header{},
34+
}
35+
traceHeaders.SetHeaders(request)
36+
37+
Expect(request.Header.Get("X-B3-TraceId")).To(Equal("new_trace_id"))
38+
Expect(request.Header.Get("X-B3-SpanId")).ToNot(BeEmpty())
39+
})
40+
})
41+
})
42+
})

api/uaa/wrapper/trace_request.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package wrapper
2+
3+
import (
4+
"net/http"
5+
6+
"code.cloudfoundry.org/cli/api/shared"
7+
"code.cloudfoundry.org/cli/api/uaa"
8+
)
9+
10+
// UAATraceHeaderRequest is a wrapper that adds b3 trace headers to requests.
11+
type UAATraceHeaderRequest struct {
12+
headers *shared.TraceHeaders
13+
connection uaa.Connection
14+
}
15+
16+
// NewUAATraceHeaderRequest returns a pointer to a UAATraceHeaderRequest wrapper.
17+
func NewUAATraceHeaderRequest(trace string) *UAATraceHeaderRequest {
18+
return &UAATraceHeaderRequest{
19+
headers: shared.NewTraceHeaders(trace),
20+
}
21+
}
22+
23+
// Add tracing headers
24+
func (t *UAATraceHeaderRequest) Make(request *http.Request, passedResponse *uaa.Response) error {
25+
t.headers.SetHeaders(request)
26+
return t.connection.Make(request, passedResponse)
27+
}
28+
29+
// Wrap sets the connection in the UAATraceHeaderRequest and returns itself.
30+
func (t *UAATraceHeaderRequest) Wrap(innerconnection uaa.Connection) uaa.Connection {
31+
t.connection = innerconnection
32+
return t
33+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package wrapper_test
2+
3+
import (
4+
"bytes"
5+
"net/http"
6+
7+
"code.cloudfoundry.org/cli/api/uaa"
8+
"code.cloudfoundry.org/cli/api/uaa/uaafakes"
9+
. "code.cloudfoundry.org/cli/api/uaa/wrapper"
10+
11+
. "github.com/onsi/ginkgo/v2"
12+
. "github.com/onsi/gomega"
13+
)
14+
15+
var _ = Describe("CCTraceHeaderRequest", func() {
16+
var (
17+
fakeConnection *uaafakes.FakeConnection
18+
19+
wrapper uaa.Connection
20+
21+
request *http.Request
22+
response *uaa.Response
23+
makeErr error
24+
25+
traceHeader string
26+
)
27+
28+
BeforeEach(func() {
29+
fakeConnection = new(uaafakes.FakeConnection)
30+
31+
traceHeader = "trace-id"
32+
33+
wrapper = NewUAATraceHeaderRequest(traceHeader).Wrap(fakeConnection)
34+
35+
body := bytes.NewReader([]byte("foo"))
36+
37+
var err error
38+
request, err = http.NewRequest(http.MethodGet, "https://foo.bar.com/banana", body)
39+
Expect(err).NotTo(HaveOccurred())
40+
41+
response = &uaa.Response{
42+
RawResponse: []byte("some-response-body"),
43+
HTTPResponse: &http.Response{},
44+
}
45+
})
46+
47+
JustBeforeEach(func() {
48+
makeErr = wrapper.Make(request, response)
49+
})
50+
51+
Describe("Make", func() {
52+
It("Adds the request headers", func() {
53+
Expect(makeErr).NotTo(HaveOccurred())
54+
Expect(request.Header.Get("X-B3-TraceId")).To(Equal(traceHeader))
55+
Expect(request.Header.Get("X-B3-SpanId")).ToNot(BeEmpty())
56+
})
57+
58+
It("Calls the inner connection", func() {
59+
Expect(fakeConnection.MakeCallCount()).To(Equal(1))
60+
req, resp := fakeConnection.MakeArgsForCall(0)
61+
Expect(req).To(Equal(request))
62+
Expect(resp).To(Equal(response))
63+
})
64+
})
65+
})

0 commit comments

Comments
 (0)