| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434 |
- // Copyright The OpenTelemetry Authors
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- package otlptracehttp_test
- import (
- "context"
- "errors"
- "fmt"
- "net/http"
- "os"
- "strings"
- "testing"
- "time"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
- "go.opentelemetry.io/otel"
- "go.opentelemetry.io/otel/exporters/otlp/otlptrace"
- "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
- "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp/internal/otlptracetest"
- coltracepb "go.opentelemetry.io/proto/otlp/collector/trace/v1"
- )
- const (
- relOtherTracesPath = "post/traces/here"
- otherTracesPath = "/post/traces/here"
- )
- var (
- testHeaders = map[string]string{
- "Otel-Go-Key-1": "somevalue",
- "Otel-Go-Key-2": "someothervalue",
- }
- customUserAgentHeader = map[string]string{
- "user-agent": "custome-user-agent",
- }
- )
- func TestEndToEnd(t *testing.T) {
- tests := []struct {
- name string
- opts []otlptracehttp.Option
- mcCfg mockCollectorConfig
- tls bool
- }{
- {
- name: "no extra options",
- opts: nil,
- },
- {
- name: "with gzip compression",
- opts: []otlptracehttp.Option{
- otlptracehttp.WithCompression(otlptracehttp.GzipCompression),
- },
- },
- {
- name: "retry",
- opts: []otlptracehttp.Option{
- otlptracehttp.WithRetry(otlptracehttp.RetryConfig{
- Enabled: true,
- InitialInterval: time.Nanosecond,
- MaxInterval: time.Nanosecond,
- // Do not stop trying.
- MaxElapsedTime: 0,
- }),
- },
- mcCfg: mockCollectorConfig{
- InjectHTTPStatus: []int{503, 429},
- },
- },
- {
- name: "retry with gzip compression",
- opts: []otlptracehttp.Option{
- otlptracehttp.WithCompression(otlptracehttp.GzipCompression),
- otlptracehttp.WithRetry(otlptracehttp.RetryConfig{
- Enabled: true,
- InitialInterval: time.Nanosecond,
- MaxInterval: time.Nanosecond,
- // Do not stop trying.
- MaxElapsedTime: 0,
- }),
- },
- mcCfg: mockCollectorConfig{
- InjectHTTPStatus: []int{503, 503},
- },
- },
- {
- name: "retry with throttle",
- opts: []otlptracehttp.Option{
- otlptracehttp.WithRetry(otlptracehttp.RetryConfig{
- Enabled: true,
- InitialInterval: time.Nanosecond,
- MaxInterval: time.Nanosecond,
- // Do not stop trying.
- MaxElapsedTime: 0,
- }),
- },
- mcCfg: mockCollectorConfig{
- InjectHTTPStatus: []int{503},
- InjectResponseHeader: []map[string]string{
- {"Retry-After": "10"},
- },
- },
- },
- {
- name: "with empty paths (forced to defaults)",
- opts: []otlptracehttp.Option{
- otlptracehttp.WithURLPath(""),
- },
- },
- {
- name: "with relative paths",
- opts: []otlptracehttp.Option{
- otlptracehttp.WithURLPath(relOtherTracesPath),
- },
- mcCfg: mockCollectorConfig{
- TracesURLPath: otherTracesPath,
- },
- },
- {
- name: "with TLS",
- opts: nil,
- mcCfg: mockCollectorConfig{
- WithTLS: true,
- },
- tls: true,
- },
- {
- name: "with extra headers",
- opts: []otlptracehttp.Option{
- otlptracehttp.WithHeaders(testHeaders),
- },
- mcCfg: mockCollectorConfig{
- ExpectedHeaders: testHeaders,
- },
- },
- {
- name: "with custom user agent",
- opts: []otlptracehttp.Option{
- otlptracehttp.WithHeaders(customUserAgentHeader),
- },
- mcCfg: mockCollectorConfig{
- ExpectedHeaders: customUserAgentHeader,
- },
- },
- }
- for _, tc := range tests {
- t.Run(tc.name, func(t *testing.T) {
- mc := runMockCollector(t, tc.mcCfg)
- defer mc.MustStop(t)
- allOpts := []otlptracehttp.Option{
- otlptracehttp.WithEndpoint(mc.Endpoint()),
- }
- if tc.tls {
- tlsConfig := mc.ClientTLSConfig()
- require.NotNil(t, tlsConfig)
- allOpts = append(allOpts, otlptracehttp.WithTLSClientConfig(tlsConfig))
- } else {
- allOpts = append(allOpts, otlptracehttp.WithInsecure())
- }
- allOpts = append(allOpts, tc.opts...)
- client := otlptracehttp.NewClient(allOpts...)
- ctx := context.Background()
- exporter, err := otlptrace.New(ctx, client)
- if assert.NoError(t, err) {
- defer func() {
- assert.NoError(t, exporter.Shutdown(ctx))
- }()
- otlptracetest.RunEndToEndTest(ctx, t, exporter, mc)
- }
- })
- }
- }
- func TestExporterShutdown(t *testing.T) {
- mc := runMockCollector(t, mockCollectorConfig{})
- defer func() {
- _ = mc.Stop()
- }()
- <-time.After(5 * time.Millisecond)
- otlptracetest.RunExporterShutdownTest(t, func() otlptrace.Client {
- return otlptracehttp.NewClient(
- otlptracehttp.WithInsecure(),
- otlptracehttp.WithEndpoint(mc.endpoint),
- )
- })
- }
- func TestTimeout(t *testing.T) {
- delay := make(chan struct{})
- mcCfg := mockCollectorConfig{Delay: delay}
- mc := runMockCollector(t, mcCfg)
- defer mc.MustStop(t)
- defer func() { close(delay) }()
- client := otlptracehttp.NewClient(
- otlptracehttp.WithEndpoint(mc.Endpoint()),
- otlptracehttp.WithInsecure(),
- otlptracehttp.WithTimeout(time.Nanosecond),
- )
- ctx := context.Background()
- exporter, err := otlptrace.New(ctx, client)
- require.NoError(t, err)
- defer func() {
- assert.NoError(t, exporter.Shutdown(ctx))
- }()
- err = exporter.ExportSpans(ctx, otlptracetest.SingleReadOnlySpan())
- unwrapped := errors.Unwrap(err)
- assert.Equalf(t, true, os.IsTimeout(unwrapped), "expected timeout error, got: %v", unwrapped)
- assert.True(t, strings.HasPrefix(err.Error(), "traces export: "), err)
- }
- func TestNoRetry(t *testing.T) {
- mc := runMockCollector(t, mockCollectorConfig{
- InjectHTTPStatus: []int{http.StatusBadRequest},
- })
- defer mc.MustStop(t)
- driver := otlptracehttp.NewClient(
- otlptracehttp.WithEndpoint(mc.Endpoint()),
- otlptracehttp.WithInsecure(),
- otlptracehttp.WithRetry(otlptracehttp.RetryConfig{
- Enabled: true,
- InitialInterval: 1 * time.Nanosecond,
- MaxInterval: 1 * time.Nanosecond,
- // Never stop retry of retry-able status.
- MaxElapsedTime: 0,
- }),
- )
- ctx := context.Background()
- exporter, err := otlptrace.New(ctx, driver)
- require.NoError(t, err)
- defer func() {
- assert.NoError(t, exporter.Shutdown(ctx))
- }()
- err = exporter.ExportSpans(ctx, otlptracetest.SingleReadOnlySpan())
- assert.Error(t, err)
- unwrapped := errors.Unwrap(err)
- assert.Equal(t, fmt.Sprintf("failed to send to http://%s/v1/traces: 400 Bad Request", mc.endpoint), unwrapped.Error())
- assert.True(t, strings.HasPrefix(err.Error(), "traces export: "))
- assert.Empty(t, mc.GetSpans())
- }
- func TestEmptyData(t *testing.T) {
- mcCfg := mockCollectorConfig{}
- mc := runMockCollector(t, mcCfg)
- defer mc.MustStop(t)
- driver := otlptracehttp.NewClient(
- otlptracehttp.WithEndpoint(mc.Endpoint()),
- otlptracehttp.WithInsecure(),
- )
- ctx := context.Background()
- exporter, err := otlptrace.New(ctx, driver)
- require.NoError(t, err)
- defer func() {
- assert.NoError(t, exporter.Shutdown(ctx))
- }()
- assert.NoError(t, err)
- err = exporter.ExportSpans(ctx, nil)
- assert.NoError(t, err)
- assert.Empty(t, mc.GetSpans())
- }
- func TestCancelledContext(t *testing.T) {
- mcCfg := mockCollectorConfig{}
- mc := runMockCollector(t, mcCfg)
- defer mc.MustStop(t)
- driver := otlptracehttp.NewClient(
- otlptracehttp.WithEndpoint(mc.Endpoint()),
- otlptracehttp.WithInsecure(),
- )
- ctx, cancel := context.WithCancel(context.Background())
- exporter, err := otlptrace.New(ctx, driver)
- require.NoError(t, err)
- defer func() {
- assert.NoError(t, exporter.Shutdown(context.Background()))
- }()
- cancel()
- err = exporter.ExportSpans(ctx, otlptracetest.SingleReadOnlySpan())
- assert.Error(t, err)
- assert.Empty(t, mc.GetSpans())
- }
- func TestDeadlineContext(t *testing.T) {
- statuses := make([]int, 0, 5)
- for i := 0; i < cap(statuses); i++ {
- statuses = append(statuses, http.StatusTooManyRequests)
- }
- mcCfg := mockCollectorConfig{
- InjectHTTPStatus: statuses,
- }
- mc := runMockCollector(t, mcCfg)
- defer mc.MustStop(t)
- driver := otlptracehttp.NewClient(
- otlptracehttp.WithEndpoint(mc.Endpoint()),
- otlptracehttp.WithInsecure(),
- otlptracehttp.WithRetry(otlptracehttp.RetryConfig{
- Enabled: true,
- InitialInterval: 1 * time.Hour,
- MaxInterval: 1 * time.Hour,
- // Never stop retry of retry-able status.
- MaxElapsedTime: 0,
- }),
- )
- ctx := context.Background()
- exporter, err := otlptrace.New(ctx, driver)
- require.NoError(t, err)
- defer func() {
- assert.NoError(t, exporter.Shutdown(context.Background()))
- }()
- ctx, cancel := context.WithTimeout(ctx, time.Second)
- defer cancel()
- err = exporter.ExportSpans(ctx, otlptracetest.SingleReadOnlySpan())
- assert.Error(t, err)
- assert.Empty(t, mc.GetSpans())
- }
- func TestStopWhileExportingConcurrentSafe(t *testing.T) {
- statuses := make([]int, 0, 5)
- for i := 0; i < cap(statuses); i++ {
- statuses = append(statuses, http.StatusTooManyRequests)
- }
- mcCfg := mockCollectorConfig{
- InjectHTTPStatus: statuses,
- }
- mc := runMockCollector(t, mcCfg)
- defer mc.MustStop(t)
- driver := otlptracehttp.NewClient(
- otlptracehttp.WithEndpoint(mc.Endpoint()),
- otlptracehttp.WithInsecure(),
- otlptracehttp.WithRetry(otlptracehttp.RetryConfig{
- Enabled: true,
- InitialInterval: 1 * time.Hour,
- MaxInterval: 1 * time.Hour,
- // Never stop retry of retry-able status.
- MaxElapsedTime: 0,
- }),
- )
- ctx := context.Background()
- exporter, err := otlptrace.New(ctx, driver)
- require.NoError(t, err)
- defer func() {
- assert.NoError(t, exporter.Shutdown(ctx))
- }()
- doneCh := make(chan struct{})
- go func() {
- err := exporter.ExportSpans(ctx, otlptracetest.SingleReadOnlySpan())
- assert.Error(t, err)
- assert.Empty(t, mc.GetSpans())
- close(doneCh)
- }()
- <-time.After(time.Second)
- err = exporter.Shutdown(ctx)
- assert.NoError(t, err)
- <-doneCh
- }
- func TestPartialSuccess(t *testing.T) {
- mcCfg := mockCollectorConfig{
- Partial: &coltracepb.ExportTracePartialSuccess{
- RejectedSpans: 2,
- ErrorMessage: "partially successful",
- },
- }
- mc := runMockCollector(t, mcCfg)
- defer mc.MustStop(t)
- driver := otlptracehttp.NewClient(
- otlptracehttp.WithEndpoint(mc.Endpoint()),
- otlptracehttp.WithInsecure(),
- )
- ctx := context.Background()
- exporter, err := otlptrace.New(ctx, driver)
- require.NoError(t, err)
- defer func() {
- assert.NoError(t, exporter.Shutdown(context.Background()))
- }()
- errs := []error{}
- otel.SetErrorHandler(otel.ErrorHandlerFunc(func(err error) {
- errs = append(errs, err)
- }))
- err = exporter.ExportSpans(ctx, otlptracetest.SingleReadOnlySpan())
- assert.NoError(t, err)
- require.Equal(t, 1, len(errs))
- require.Contains(t, errs[0].Error(), "partially successful")
- require.Contains(t, errs[0].Error(), "2 spans rejected")
- }
- func TestOtherHTTPSuccess(t *testing.T) {
- for code := 201; code <= 299; code++ {
- t.Run(fmt.Sprintf("status_%d", code), func(t *testing.T) {
- mcCfg := mockCollectorConfig{
- InjectHTTPStatus: []int{code},
- }
- mc := runMockCollector(t, mcCfg)
- defer mc.MustStop(t)
- driver := otlptracehttp.NewClient(
- otlptracehttp.WithEndpoint(mc.Endpoint()),
- otlptracehttp.WithInsecure(),
- )
- ctx := context.Background()
- exporter, err := otlptrace.New(ctx, driver)
- require.NoError(t, err)
- defer func() {
- assert.NoError(t, exporter.Shutdown(context.Background()))
- }()
- errs := []error{}
- otel.SetErrorHandler(otel.ErrorHandlerFunc(func(err error) {
- errs = append(errs, err)
- }))
- err = exporter.ExportSpans(ctx, otlptracetest.SingleReadOnlySpan())
- assert.NoError(t, err)
- assert.Equal(t, 0, len(errs))
- })
- }
- }
|