Skip to content

Commit 8d80981

Browse files
authored
Move gRPC driver to a subpackage and add an HTTP driver (#1420)
* Move grpc stuff to separate package * Drop duplicated retryable status code * Set default port to 4317 This is what the specification says for both gRPC and HTTP. * Document gRPC option type * Add an HTTP protocol driver for OTLP exporter Currently it supports only binary protobuf payloads. * Move end to end test to a separate package It also adds some common code mock collectors can use. This will be useful for testing the HTTP driver. * Move export data creators to otlptest It also extends the one record checkpointer a bit. This will be useful for testing the HTTP driver. * Add an HTTP mock collector and tests for HTTP driver * Update changelog * Do not depend on DefaultTransport We create our own instance of the transport, which is based on golang's DefaultTransport. That way we sidestep the issue of the DefaultTransport being modified/overwritten. We won't have any panics at init. The cost of it is to keep the transport fields in sync with DefaultTransport. * Read the whole response body before closing it This may help with connection reuse. * Change options to conform to our style guide * Add jitter to backoff time * Test TLS option * Test extra headers * Fix a comment * Increase coverage * Add a source of the backoff strategy
1 parent 9332af1 commit 8d80981

23 files changed

+2605
-1031
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
1616
- `NewSplitDriver` for OTLP exporter that allows sending traces and metrics to different endpoints. (#1418)
1717
- Add codeql worfklow to GitHub Actions (#1428)
1818
- Added Gosec workflow to GitHub Actions (#1429)
19+
- A new HTTP driver for OTLP exporter in `exporters/otlp/otlphttp`. Currently it only supports the binary protobuf payloads. (#1420)
1920

2021
### Changed
2122

@@ -29,6 +30,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
2930
- Unify endpoint API that related to OTel exporter. (#1401)
3031
- Metric aggregator Count() and histogram Bucket.Counts are consistently `uint64`. (1430)
3132
- `SamplingResult` now passed a `Tracestate` from the parent `SpanContext` (#1432)
33+
- Moved gRPC driver for OTLP exporter to `exporters/otlp/otlpgrpc`. (#1420)
3234

3335
### Removed
3436

example/otel-collector/main.go

+5-4
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727

2828
"go.opentelemetry.io/otel"
2929
"go.opentelemetry.io/otel/exporters/otlp"
30+
"go.opentelemetry.io/otel/exporters/otlp/otlpgrpc"
3031
"go.opentelemetry.io/otel/label"
3132
"go.opentelemetry.io/otel/metric"
3233
"go.opentelemetry.io/otel/propagation"
@@ -49,10 +50,10 @@ func initProvider() func() {
4950
// `localhost:30080` endpoint. Otherwise, replace `localhost` with the
5051
// endpoint of your cluster. If you run the app inside k8s, then you can
5152
// probably connect directly to the service through dns
52-
driver := otlp.NewGRPCDriver(
53-
otlp.WithInsecure(),
54-
otlp.WithEndpoint("localhost:30080"),
55-
otlp.WithGRPCDialOption(grpc.WithBlock()), // useful for testing
53+
driver := otlpgrpc.NewDriver(
54+
otlpgrpc.WithInsecure(),
55+
otlpgrpc.WithEndpoint("localhost:30080"),
56+
otlpgrpc.WithDialOption(grpc.WithBlock()), // useful for testing
5657
)
5758
exp, err := otlp.NewExporter(ctx, driver)
5859
handleErr(err, "failed to create exporter")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
// Copyright The OpenTelemetry Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package otlptest
16+
17+
import (
18+
"sort"
19+
20+
collectormetricpb "go.opentelemetry.io/otel/exporters/otlp/internal/opentelemetry-proto-gen/collector/metrics/v1"
21+
collectortracepb "go.opentelemetry.io/otel/exporters/otlp/internal/opentelemetry-proto-gen/collector/trace/v1"
22+
commonpb "go.opentelemetry.io/otel/exporters/otlp/internal/opentelemetry-proto-gen/common/v1"
23+
metricpb "go.opentelemetry.io/otel/exporters/otlp/internal/opentelemetry-proto-gen/metrics/v1"
24+
resourcepb "go.opentelemetry.io/otel/exporters/otlp/internal/opentelemetry-proto-gen/resource/v1"
25+
tracepb "go.opentelemetry.io/otel/exporters/otlp/internal/opentelemetry-proto-gen/trace/v1"
26+
)
27+
28+
// Collector is an interface that mock collectors should implements,
29+
// so they can be used for the end-to-end testing.
30+
type Collector interface {
31+
Stop() error
32+
GetResourceSpans() []*tracepb.ResourceSpans
33+
GetMetrics() []*metricpb.Metric
34+
}
35+
36+
// SpansStorage stores the spans. Mock collectors could use it to
37+
// store spans they have received.
38+
type SpansStorage struct {
39+
rsm map[string]*tracepb.ResourceSpans
40+
spanCount int
41+
}
42+
43+
// MetricsStorage stores the metrics. Mock collectors could use it to
44+
// store metrics they have received.
45+
type MetricsStorage struct {
46+
metrics []*metricpb.Metric
47+
}
48+
49+
// NewSpansStorage creates a new spans storage.
50+
func NewSpansStorage() SpansStorage {
51+
return SpansStorage{
52+
rsm: make(map[string]*tracepb.ResourceSpans),
53+
}
54+
}
55+
56+
// AddSpans adds spans to the spans storage.
57+
func (s *SpansStorage) AddSpans(request *collectortracepb.ExportTraceServiceRequest) {
58+
for _, rs := range request.GetResourceSpans() {
59+
rstr := resourceString(rs.Resource)
60+
if existingRs, ok := s.rsm[rstr]; !ok {
61+
s.rsm[rstr] = rs
62+
// TODO (rghetia): Add support for library Info.
63+
if len(rs.InstrumentationLibrarySpans) == 0 {
64+
rs.InstrumentationLibrarySpans = []*tracepb.InstrumentationLibrarySpans{
65+
{
66+
Spans: []*tracepb.Span{},
67+
},
68+
}
69+
}
70+
s.spanCount += len(rs.InstrumentationLibrarySpans[0].Spans)
71+
} else {
72+
if len(rs.InstrumentationLibrarySpans) > 0 {
73+
newSpans := rs.InstrumentationLibrarySpans[0].GetSpans()
74+
existingRs.InstrumentationLibrarySpans[0].Spans =
75+
append(existingRs.InstrumentationLibrarySpans[0].Spans,
76+
newSpans...)
77+
s.spanCount += len(newSpans)
78+
}
79+
}
80+
}
81+
}
82+
83+
// GetSpans returns the stored spans.
84+
func (s *SpansStorage) GetSpans() []*tracepb.Span {
85+
spans := make([]*tracepb.Span, 0, s.spanCount)
86+
for _, rs := range s.rsm {
87+
spans = append(spans, rs.InstrumentationLibrarySpans[0].Spans...)
88+
}
89+
return spans
90+
}
91+
92+
// GetResourceSpans returns the stored resource spans.
93+
func (s *SpansStorage) GetResourceSpans() []*tracepb.ResourceSpans {
94+
rss := make([]*tracepb.ResourceSpans, 0, len(s.rsm))
95+
for _, rs := range s.rsm {
96+
rss = append(rss, rs)
97+
}
98+
return rss
99+
}
100+
101+
// NewMetricsStorage creates a new metrics storage.
102+
func NewMetricsStorage() MetricsStorage {
103+
return MetricsStorage{}
104+
}
105+
106+
// AddMetrics adds metrics to the metrics storage.
107+
func (s *MetricsStorage) AddMetrics(request *collectormetricpb.ExportMetricsServiceRequest) {
108+
for _, rm := range request.GetResourceMetrics() {
109+
// TODO (rghetia) handle multiple resource and library info.
110+
if len(rm.InstrumentationLibraryMetrics) > 0 {
111+
s.metrics = append(s.metrics, rm.InstrumentationLibraryMetrics[0].Metrics...)
112+
}
113+
}
114+
}
115+
116+
// GetMetrics returns the stored metrics.
117+
func (s *MetricsStorage) GetMetrics() []*metricpb.Metric {
118+
// copy in order to not change.
119+
m := make([]*metricpb.Metric, 0, len(s.metrics))
120+
return append(m, s.metrics...)
121+
}
122+
123+
func resourceString(res *resourcepb.Resource) string {
124+
sAttrs := sortedAttributes(res.GetAttributes())
125+
rstr := ""
126+
for _, attr := range sAttrs {
127+
rstr = rstr + attr.String()
128+
}
129+
return rstr
130+
}
131+
132+
func sortedAttributes(attrs []*commonpb.KeyValue) []*commonpb.KeyValue {
133+
sort.Slice(attrs[:], func(i, j int) bool {
134+
return attrs[i].Key < attrs[j].Key
135+
})
136+
return attrs
137+
}
+138
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
// Copyright The OpenTelemetry Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package otlptest
16+
17+
import (
18+
"context"
19+
"fmt"
20+
"time"
21+
22+
"go.opentelemetry.io/otel/codes"
23+
"go.opentelemetry.io/otel/label"
24+
"go.opentelemetry.io/otel/metric"
25+
"go.opentelemetry.io/otel/metric/number"
26+
exportmetric "go.opentelemetry.io/otel/sdk/export/metric"
27+
exporttrace "go.opentelemetry.io/otel/sdk/export/trace"
28+
"go.opentelemetry.io/otel/sdk/instrumentation"
29+
"go.opentelemetry.io/otel/sdk/metric/aggregator/sum"
30+
"go.opentelemetry.io/otel/sdk/resource"
31+
"go.opentelemetry.io/otel/trace"
32+
)
33+
34+
// Used to avoid implementing locking functions for test
35+
// checkpointsets.
36+
type noopLocker struct{}
37+
38+
// Lock implements sync.Locker, which is needed for
39+
// exportmetric.CheckpointSet.
40+
func (noopLocker) Lock() {}
41+
42+
// Unlock implements sync.Locker, which is needed for
43+
// exportmetric.CheckpointSet.
44+
func (noopLocker) Unlock() {}
45+
46+
// RLock implements exportmetric.CheckpointSet.
47+
func (noopLocker) RLock() {}
48+
49+
// RUnlock implements exportmetric.CheckpointSet.
50+
func (noopLocker) RUnlock() {}
51+
52+
// OneRecordCheckpointSet is a CheckpointSet that returns just one
53+
// filled record. It may be useful for testing driver's metrics
54+
// export.
55+
type OneRecordCheckpointSet struct {
56+
noopLocker
57+
}
58+
59+
var _ exportmetric.CheckpointSet = OneRecordCheckpointSet{}
60+
61+
// ForEach implements exportmetric.CheckpointSet. It always invokes
62+
// the callback once with always the same record.
63+
func (OneRecordCheckpointSet) ForEach(kindSelector exportmetric.ExportKindSelector, recordFunc func(exportmetric.Record) error) error {
64+
desc := metric.NewDescriptor(
65+
"foo",
66+
metric.CounterInstrumentKind,
67+
number.Int64Kind,
68+
)
69+
res := resource.NewWithAttributes(label.String("a", "b"))
70+
agg := sum.New(1)
71+
if err := agg[0].Update(context.Background(), number.NewInt64Number(42), &desc); err != nil {
72+
return err
73+
}
74+
start := time.Date(2020, time.December, 8, 19, 15, 0, 0, time.UTC)
75+
end := time.Date(2020, time.December, 8, 19, 16, 0, 0, time.UTC)
76+
labels := label.NewSet(label.String("abc", "def"), label.Int64("one", 1))
77+
rec := exportmetric.NewRecord(&desc, &labels, res, agg[0].Aggregation(), start, end)
78+
return recordFunc(rec)
79+
}
80+
81+
// SingleSpanSnapshot returns a one-element slice with a snapshot. It
82+
// may be useful for testing driver's trace export.
83+
func SingleSpanSnapshot() []*exporttrace.SpanSnapshot {
84+
sd := &exporttrace.SpanSnapshot{
85+
SpanContext: trace.SpanContext{
86+
TraceID: trace.TraceID{2, 3, 4, 5, 6, 7, 8, 9, 2, 3, 4, 5, 6, 7, 8, 9},
87+
SpanID: trace.SpanID{3, 4, 5, 6, 7, 8, 9, 0},
88+
TraceFlags: trace.FlagsSampled,
89+
},
90+
ParentSpanID: trace.SpanID{1, 2, 3, 4, 5, 6, 7, 8},
91+
SpanKind: trace.SpanKindInternal,
92+
Name: "foo",
93+
StartTime: time.Date(2020, time.December, 8, 20, 23, 0, 0, time.UTC),
94+
EndTime: time.Date(2020, time.December, 0, 20, 24, 0, 0, time.UTC),
95+
Attributes: []label.KeyValue{},
96+
MessageEvents: []exporttrace.Event{},
97+
Links: []trace.Link{},
98+
StatusCode: codes.Ok,
99+
StatusMessage: "",
100+
HasRemoteParent: false,
101+
DroppedAttributeCount: 0,
102+
DroppedMessageEventCount: 0,
103+
DroppedLinkCount: 0,
104+
ChildSpanCount: 0,
105+
Resource: resource.NewWithAttributes(label.String("a", "b")),
106+
InstrumentationLibrary: instrumentation.Library{
107+
Name: "bar",
108+
Version: "0.0.0",
109+
},
110+
}
111+
return []*exporttrace.SpanSnapshot{sd}
112+
}
113+
114+
// EmptyCheckpointSet is a checkpointer that has no records at all.
115+
type EmptyCheckpointSet struct {
116+
noopLocker
117+
}
118+
119+
var _ exportmetric.CheckpointSet = EmptyCheckpointSet{}
120+
121+
// ForEach implements exportmetric.CheckpointSet. It never invokes the
122+
// callback.
123+
func (EmptyCheckpointSet) ForEach(kindSelector exportmetric.ExportKindSelector, recordFunc func(exportmetric.Record) error) error {
124+
return nil
125+
}
126+
127+
// FailCheckpointSet is a checkpointer that returns an error during
128+
// ForEach.
129+
type FailCheckpointSet struct {
130+
noopLocker
131+
}
132+
133+
var _ exportmetric.CheckpointSet = FailCheckpointSet{}
134+
135+
// ForEach implements exportmetric.CheckpointSet. It always fails.
136+
func (FailCheckpointSet) ForEach(kindSelector exportmetric.ExportKindSelector, recordFunc func(exportmetric.Record) error) error {
137+
return fmt.Errorf("fail")
138+
}

0 commit comments

Comments
 (0)