client_test.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434
  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. package otlptracehttp_test
  15. import (
  16. "context"
  17. "errors"
  18. "fmt"
  19. "net/http"
  20. "os"
  21. "strings"
  22. "testing"
  23. "time"
  24. "github.com/stretchr/testify/assert"
  25. "github.com/stretchr/testify/require"
  26. "go.opentelemetry.io/otel"
  27. "go.opentelemetry.io/otel/exporters/otlp/otlptrace"
  28. "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
  29. "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp/internal/otlptracetest"
  30. coltracepb "go.opentelemetry.io/proto/otlp/collector/trace/v1"
  31. )
  32. const (
  33. relOtherTracesPath = "post/traces/here"
  34. otherTracesPath = "/post/traces/here"
  35. )
  36. var (
  37. testHeaders = map[string]string{
  38. "Otel-Go-Key-1": "somevalue",
  39. "Otel-Go-Key-2": "someothervalue",
  40. }
  41. customUserAgentHeader = map[string]string{
  42. "user-agent": "custome-user-agent",
  43. }
  44. )
  45. func TestEndToEnd(t *testing.T) {
  46. tests := []struct {
  47. name string
  48. opts []otlptracehttp.Option
  49. mcCfg mockCollectorConfig
  50. tls bool
  51. }{
  52. {
  53. name: "no extra options",
  54. opts: nil,
  55. },
  56. {
  57. name: "with gzip compression",
  58. opts: []otlptracehttp.Option{
  59. otlptracehttp.WithCompression(otlptracehttp.GzipCompression),
  60. },
  61. },
  62. {
  63. name: "retry",
  64. opts: []otlptracehttp.Option{
  65. otlptracehttp.WithRetry(otlptracehttp.RetryConfig{
  66. Enabled: true,
  67. InitialInterval: time.Nanosecond,
  68. MaxInterval: time.Nanosecond,
  69. // Do not stop trying.
  70. MaxElapsedTime: 0,
  71. }),
  72. },
  73. mcCfg: mockCollectorConfig{
  74. InjectHTTPStatus: []int{503, 429},
  75. },
  76. },
  77. {
  78. name: "retry with gzip compression",
  79. opts: []otlptracehttp.Option{
  80. otlptracehttp.WithCompression(otlptracehttp.GzipCompression),
  81. otlptracehttp.WithRetry(otlptracehttp.RetryConfig{
  82. Enabled: true,
  83. InitialInterval: time.Nanosecond,
  84. MaxInterval: time.Nanosecond,
  85. // Do not stop trying.
  86. MaxElapsedTime: 0,
  87. }),
  88. },
  89. mcCfg: mockCollectorConfig{
  90. InjectHTTPStatus: []int{503, 503},
  91. },
  92. },
  93. {
  94. name: "retry with throttle",
  95. opts: []otlptracehttp.Option{
  96. otlptracehttp.WithRetry(otlptracehttp.RetryConfig{
  97. Enabled: true,
  98. InitialInterval: time.Nanosecond,
  99. MaxInterval: time.Nanosecond,
  100. // Do not stop trying.
  101. MaxElapsedTime: 0,
  102. }),
  103. },
  104. mcCfg: mockCollectorConfig{
  105. InjectHTTPStatus: []int{503},
  106. InjectResponseHeader: []map[string]string{
  107. {"Retry-After": "10"},
  108. },
  109. },
  110. },
  111. {
  112. name: "with empty paths (forced to defaults)",
  113. opts: []otlptracehttp.Option{
  114. otlptracehttp.WithURLPath(""),
  115. },
  116. },
  117. {
  118. name: "with relative paths",
  119. opts: []otlptracehttp.Option{
  120. otlptracehttp.WithURLPath(relOtherTracesPath),
  121. },
  122. mcCfg: mockCollectorConfig{
  123. TracesURLPath: otherTracesPath,
  124. },
  125. },
  126. {
  127. name: "with TLS",
  128. opts: nil,
  129. mcCfg: mockCollectorConfig{
  130. WithTLS: true,
  131. },
  132. tls: true,
  133. },
  134. {
  135. name: "with extra headers",
  136. opts: []otlptracehttp.Option{
  137. otlptracehttp.WithHeaders(testHeaders),
  138. },
  139. mcCfg: mockCollectorConfig{
  140. ExpectedHeaders: testHeaders,
  141. },
  142. },
  143. {
  144. name: "with custom user agent",
  145. opts: []otlptracehttp.Option{
  146. otlptracehttp.WithHeaders(customUserAgentHeader),
  147. },
  148. mcCfg: mockCollectorConfig{
  149. ExpectedHeaders: customUserAgentHeader,
  150. },
  151. },
  152. }
  153. for _, tc := range tests {
  154. t.Run(tc.name, func(t *testing.T) {
  155. mc := runMockCollector(t, tc.mcCfg)
  156. defer mc.MustStop(t)
  157. allOpts := []otlptracehttp.Option{
  158. otlptracehttp.WithEndpoint(mc.Endpoint()),
  159. }
  160. if tc.tls {
  161. tlsConfig := mc.ClientTLSConfig()
  162. require.NotNil(t, tlsConfig)
  163. allOpts = append(allOpts, otlptracehttp.WithTLSClientConfig(tlsConfig))
  164. } else {
  165. allOpts = append(allOpts, otlptracehttp.WithInsecure())
  166. }
  167. allOpts = append(allOpts, tc.opts...)
  168. client := otlptracehttp.NewClient(allOpts...)
  169. ctx := context.Background()
  170. exporter, err := otlptrace.New(ctx, client)
  171. if assert.NoError(t, err) {
  172. defer func() {
  173. assert.NoError(t, exporter.Shutdown(ctx))
  174. }()
  175. otlptracetest.RunEndToEndTest(ctx, t, exporter, mc)
  176. }
  177. })
  178. }
  179. }
  180. func TestExporterShutdown(t *testing.T) {
  181. mc := runMockCollector(t, mockCollectorConfig{})
  182. defer func() {
  183. _ = mc.Stop()
  184. }()
  185. <-time.After(5 * time.Millisecond)
  186. otlptracetest.RunExporterShutdownTest(t, func() otlptrace.Client {
  187. return otlptracehttp.NewClient(
  188. otlptracehttp.WithInsecure(),
  189. otlptracehttp.WithEndpoint(mc.endpoint),
  190. )
  191. })
  192. }
  193. func TestTimeout(t *testing.T) {
  194. delay := make(chan struct{})
  195. mcCfg := mockCollectorConfig{Delay: delay}
  196. mc := runMockCollector(t, mcCfg)
  197. defer mc.MustStop(t)
  198. defer func() { close(delay) }()
  199. client := otlptracehttp.NewClient(
  200. otlptracehttp.WithEndpoint(mc.Endpoint()),
  201. otlptracehttp.WithInsecure(),
  202. otlptracehttp.WithTimeout(time.Nanosecond),
  203. )
  204. ctx := context.Background()
  205. exporter, err := otlptrace.New(ctx, client)
  206. require.NoError(t, err)
  207. defer func() {
  208. assert.NoError(t, exporter.Shutdown(ctx))
  209. }()
  210. err = exporter.ExportSpans(ctx, otlptracetest.SingleReadOnlySpan())
  211. unwrapped := errors.Unwrap(err)
  212. assert.Equalf(t, true, os.IsTimeout(unwrapped), "expected timeout error, got: %v", unwrapped)
  213. assert.True(t, strings.HasPrefix(err.Error(), "traces export: "), err)
  214. }
  215. func TestNoRetry(t *testing.T) {
  216. mc := runMockCollector(t, mockCollectorConfig{
  217. InjectHTTPStatus: []int{http.StatusBadRequest},
  218. })
  219. defer mc.MustStop(t)
  220. driver := otlptracehttp.NewClient(
  221. otlptracehttp.WithEndpoint(mc.Endpoint()),
  222. otlptracehttp.WithInsecure(),
  223. otlptracehttp.WithRetry(otlptracehttp.RetryConfig{
  224. Enabled: true,
  225. InitialInterval: 1 * time.Nanosecond,
  226. MaxInterval: 1 * time.Nanosecond,
  227. // Never stop retry of retry-able status.
  228. MaxElapsedTime: 0,
  229. }),
  230. )
  231. ctx := context.Background()
  232. exporter, err := otlptrace.New(ctx, driver)
  233. require.NoError(t, err)
  234. defer func() {
  235. assert.NoError(t, exporter.Shutdown(ctx))
  236. }()
  237. err = exporter.ExportSpans(ctx, otlptracetest.SingleReadOnlySpan())
  238. assert.Error(t, err)
  239. unwrapped := errors.Unwrap(err)
  240. assert.Equal(t, fmt.Sprintf("failed to send to http://%s/v1/traces: 400 Bad Request", mc.endpoint), unwrapped.Error())
  241. assert.True(t, strings.HasPrefix(err.Error(), "traces export: "))
  242. assert.Empty(t, mc.GetSpans())
  243. }
  244. func TestEmptyData(t *testing.T) {
  245. mcCfg := mockCollectorConfig{}
  246. mc := runMockCollector(t, mcCfg)
  247. defer mc.MustStop(t)
  248. driver := otlptracehttp.NewClient(
  249. otlptracehttp.WithEndpoint(mc.Endpoint()),
  250. otlptracehttp.WithInsecure(),
  251. )
  252. ctx := context.Background()
  253. exporter, err := otlptrace.New(ctx, driver)
  254. require.NoError(t, err)
  255. defer func() {
  256. assert.NoError(t, exporter.Shutdown(ctx))
  257. }()
  258. assert.NoError(t, err)
  259. err = exporter.ExportSpans(ctx, nil)
  260. assert.NoError(t, err)
  261. assert.Empty(t, mc.GetSpans())
  262. }
  263. func TestCancelledContext(t *testing.T) {
  264. mcCfg := mockCollectorConfig{}
  265. mc := runMockCollector(t, mcCfg)
  266. defer mc.MustStop(t)
  267. driver := otlptracehttp.NewClient(
  268. otlptracehttp.WithEndpoint(mc.Endpoint()),
  269. otlptracehttp.WithInsecure(),
  270. )
  271. ctx, cancel := context.WithCancel(context.Background())
  272. exporter, err := otlptrace.New(ctx, driver)
  273. require.NoError(t, err)
  274. defer func() {
  275. assert.NoError(t, exporter.Shutdown(context.Background()))
  276. }()
  277. cancel()
  278. err = exporter.ExportSpans(ctx, otlptracetest.SingleReadOnlySpan())
  279. assert.Error(t, err)
  280. assert.Empty(t, mc.GetSpans())
  281. }
  282. func TestDeadlineContext(t *testing.T) {
  283. statuses := make([]int, 0, 5)
  284. for i := 0; i < cap(statuses); i++ {
  285. statuses = append(statuses, http.StatusTooManyRequests)
  286. }
  287. mcCfg := mockCollectorConfig{
  288. InjectHTTPStatus: statuses,
  289. }
  290. mc := runMockCollector(t, mcCfg)
  291. defer mc.MustStop(t)
  292. driver := otlptracehttp.NewClient(
  293. otlptracehttp.WithEndpoint(mc.Endpoint()),
  294. otlptracehttp.WithInsecure(),
  295. otlptracehttp.WithRetry(otlptracehttp.RetryConfig{
  296. Enabled: true,
  297. InitialInterval: 1 * time.Hour,
  298. MaxInterval: 1 * time.Hour,
  299. // Never stop retry of retry-able status.
  300. MaxElapsedTime: 0,
  301. }),
  302. )
  303. ctx := context.Background()
  304. exporter, err := otlptrace.New(ctx, driver)
  305. require.NoError(t, err)
  306. defer func() {
  307. assert.NoError(t, exporter.Shutdown(context.Background()))
  308. }()
  309. ctx, cancel := context.WithTimeout(ctx, time.Second)
  310. defer cancel()
  311. err = exporter.ExportSpans(ctx, otlptracetest.SingleReadOnlySpan())
  312. assert.Error(t, err)
  313. assert.Empty(t, mc.GetSpans())
  314. }
  315. func TestStopWhileExportingConcurrentSafe(t *testing.T) {
  316. statuses := make([]int, 0, 5)
  317. for i := 0; i < cap(statuses); i++ {
  318. statuses = append(statuses, http.StatusTooManyRequests)
  319. }
  320. mcCfg := mockCollectorConfig{
  321. InjectHTTPStatus: statuses,
  322. }
  323. mc := runMockCollector(t, mcCfg)
  324. defer mc.MustStop(t)
  325. driver := otlptracehttp.NewClient(
  326. otlptracehttp.WithEndpoint(mc.Endpoint()),
  327. otlptracehttp.WithInsecure(),
  328. otlptracehttp.WithRetry(otlptracehttp.RetryConfig{
  329. Enabled: true,
  330. InitialInterval: 1 * time.Hour,
  331. MaxInterval: 1 * time.Hour,
  332. // Never stop retry of retry-able status.
  333. MaxElapsedTime: 0,
  334. }),
  335. )
  336. ctx := context.Background()
  337. exporter, err := otlptrace.New(ctx, driver)
  338. require.NoError(t, err)
  339. defer func() {
  340. assert.NoError(t, exporter.Shutdown(ctx))
  341. }()
  342. doneCh := make(chan struct{})
  343. go func() {
  344. err := exporter.ExportSpans(ctx, otlptracetest.SingleReadOnlySpan())
  345. assert.Error(t, err)
  346. assert.Empty(t, mc.GetSpans())
  347. close(doneCh)
  348. }()
  349. <-time.After(time.Second)
  350. err = exporter.Shutdown(ctx)
  351. assert.NoError(t, err)
  352. <-doneCh
  353. }
  354. func TestPartialSuccess(t *testing.T) {
  355. mcCfg := mockCollectorConfig{
  356. Partial: &coltracepb.ExportTracePartialSuccess{
  357. RejectedSpans: 2,
  358. ErrorMessage: "partially successful",
  359. },
  360. }
  361. mc := runMockCollector(t, mcCfg)
  362. defer mc.MustStop(t)
  363. driver := otlptracehttp.NewClient(
  364. otlptracehttp.WithEndpoint(mc.Endpoint()),
  365. otlptracehttp.WithInsecure(),
  366. )
  367. ctx := context.Background()
  368. exporter, err := otlptrace.New(ctx, driver)
  369. require.NoError(t, err)
  370. defer func() {
  371. assert.NoError(t, exporter.Shutdown(context.Background()))
  372. }()
  373. errs := []error{}
  374. otel.SetErrorHandler(otel.ErrorHandlerFunc(func(err error) {
  375. errs = append(errs, err)
  376. }))
  377. err = exporter.ExportSpans(ctx, otlptracetest.SingleReadOnlySpan())
  378. assert.NoError(t, err)
  379. require.Equal(t, 1, len(errs))
  380. require.Contains(t, errs[0].Error(), "partially successful")
  381. require.Contains(t, errs[0].Error(), "2 spans rejected")
  382. }
  383. func TestOtherHTTPSuccess(t *testing.T) {
  384. for code := 201; code <= 299; code++ {
  385. t.Run(fmt.Sprintf("status_%d", code), func(t *testing.T) {
  386. mcCfg := mockCollectorConfig{
  387. InjectHTTPStatus: []int{code},
  388. }
  389. mc := runMockCollector(t, mcCfg)
  390. defer mc.MustStop(t)
  391. driver := otlptracehttp.NewClient(
  392. otlptracehttp.WithEndpoint(mc.Endpoint()),
  393. otlptracehttp.WithInsecure(),
  394. )
  395. ctx := context.Background()
  396. exporter, err := otlptrace.New(ctx, driver)
  397. require.NoError(t, err)
  398. defer func() {
  399. assert.NoError(t, exporter.Shutdown(context.Background()))
  400. }()
  401. errs := []error{}
  402. otel.SetErrorHandler(otel.ErrorHandlerFunc(func(err error) {
  403. errs = append(errs, err)
  404. }))
  405. err = exporter.ExportSpans(ctx, otlptracetest.SingleReadOnlySpan())
  406. assert.NoError(t, err)
  407. assert.Equal(t, 0, len(errs))
  408. })
  409. }
  410. }