From 8c828e62b6eb2c4e15116e7ec590695317c483d6 Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Fri, 12 Aug 2022 11:04:24 -0700 Subject: [PATCH 01/34] Add otlpmetric package doc --- exporters/otlp/otlpmetric/doc.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 exporters/otlp/otlpmetric/doc.go diff --git a/exporters/otlp/otlpmetric/doc.go b/exporters/otlp/otlpmetric/doc.go new file mode 100644 index 00000000000..31831c415fe --- /dev/null +++ b/exporters/otlp/otlpmetric/doc.go @@ -0,0 +1,20 @@ +// 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 otlpmetric provides an OpenTelemetry metric Exporter that can be +// used with PeriodicReader. It transforms metricdata into OTLP and transmits +// the transformed data to OTLP receivers. The Exporter is configurable to use +// different Clients, each using a distinct transport protocol to communicate +// to an OTLP receiving endpoint. +package otlpmetric // import "go.opentelemetry.io/otel/exporters/otlp/otlpmetric" From 65e1c94772775851362272c5f4a84a08935cfd37 Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Fri, 12 Aug 2022 11:06:20 -0700 Subject: [PATCH 02/34] Add Client interface --- exporters/otlp/otlpmetric/client.go | 56 +++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 exporters/otlp/otlpmetric/client.go diff --git a/exporters/otlp/otlpmetric/client.go b/exporters/otlp/otlpmetric/client.go new file mode 100644 index 00000000000..1c294fda8e9 --- /dev/null +++ b/exporters/otlp/otlpmetric/client.go @@ -0,0 +1,56 @@ +// 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. + +//go:build go1.18 +// +build go1.18 + +package otlpmetric // import "go.opentelemetry.io/otel/exporters/otlp/otlpmetric" + +import ( + "context" + + mpb "go.opentelemetry.io/proto/otlp/metrics/v1" +) + +// Client handles the transmission of OTLP data to an OTLP recieving endpoint. +type Client interface { + // UploadMetrics transmits metric data to an OTLP receiver. + // + // UploadMetrics is called synchronously by the Exporter, however, it may + // be called concurrently with Shutdown or ForceFlush. + // + // All retry logic must be handled by UploadMetrics alone, the Exporter + // does not implement any retry logic. All returned errors are considered + // unrecoverable. + UploadMetrics(context.Context, *mpb.ResourceMetrics) error + + // ForceFlush flushes any metric data held by an Client. + // + // The deadline or cancellation of the passed context must be honored. An + // appropriate error should be returned in these situations. + ForceFlush(context.Context) error + + // Shutdown flushes all metric data held by a Client and closes any + // connections it holds open. + // + // The deadline or cancellation of the passed context must be honored. An + // appropriate error should be returned in these situations. + // + // Shutdown may be called concurrently with UploadMetrics or ForceFlush. + // + // Shutdown will only be called once by the Exporter. Once a return value + // is recieved by the Exporter from Shutdown the Client will not be used + // anymore. + Shutdown(context.Context) error +} From 5bfc4081041cbe3afa88982321ef7c9224fd3515 Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Fri, 12 Aug 2022 12:18:10 -0700 Subject: [PATCH 03/34] Add the Exporter Have the Exporter ensure synchronous access to all client methods. --- exporters/otlp/otlpmetric/client.go | 8 +- exporters/otlp/otlpmetric/exporter.go | 103 ++++++++++++++++++++++++++ 2 files changed, 105 insertions(+), 6 deletions(-) create mode 100644 exporters/otlp/otlpmetric/exporter.go diff --git a/exporters/otlp/otlpmetric/client.go b/exporters/otlp/otlpmetric/client.go index 1c294fda8e9..0ef59e3c371 100644 --- a/exporters/otlp/otlpmetric/client.go +++ b/exporters/otlp/otlpmetric/client.go @@ -27,9 +27,6 @@ import ( type Client interface { // UploadMetrics transmits metric data to an OTLP receiver. // - // UploadMetrics is called synchronously by the Exporter, however, it may - // be called concurrently with Shutdown or ForceFlush. - // // All retry logic must be handled by UploadMetrics alone, the Exporter // does not implement any retry logic. All returned errors are considered // unrecoverable. @@ -47,10 +44,9 @@ type Client interface { // The deadline or cancellation of the passed context must be honored. An // appropriate error should be returned in these situations. // - // Shutdown may be called concurrently with UploadMetrics or ForceFlush. - // // Shutdown will only be called once by the Exporter. Once a return value // is recieved by the Exporter from Shutdown the Client will not be used - // anymore. + // anymore. Therefore all computational resources need to be released + // after this is called so the Client can be garbage collected. Shutdown(context.Context) error } diff --git a/exporters/otlp/otlpmetric/exporter.go b/exporters/otlp/otlpmetric/exporter.go new file mode 100644 index 00000000000..52b6b63b730 --- /dev/null +++ b/exporters/otlp/otlpmetric/exporter.go @@ -0,0 +1,103 @@ +// 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. + +//go:build go1.18 +// +build go1.18 + +package otlpmetric // import "go.opentelemetry.io/otel/exporters/otlp/otlpmetric" + +import ( + "context" + "fmt" + "sync" + + "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/internal/transform" + "go.opentelemetry.io/otel/sdk/metric" + "go.opentelemetry.io/otel/sdk/metric/metricdata" + mpb "go.opentelemetry.io/proto/otlp/metrics/v1" +) + +// exporter exports metrics data as OTLP. +type exporter struct { + // Ensure syncronous access to the client accross all functionality. + clientMu sync.Mutex + client Client + + shutdownOnce sync.Once +} + +// Export transforms and transmits metric data to an OTLP receiver. +func (e *exporter) Export(ctx context.Context, rm metricdata.ResourceMetrics) error { + otlpRm, tErr := transform.ResourceMetrics(rm) + // Best effort upload of transformable metrics. + e.clientMu.Lock() + upErr := e.client.UploadMetrics(ctx, otlpRm) + e.clientMu.Unlock() + if upErr != nil { + // Prioritize the upload error over the transform error. + return upErr + } + return tErr +} + +// ForceFlush flushes any metric data held by an exporter. +func (e *exporter) ForceFlush(ctx context.Context) error { + // The Exporter does not hold data, forward the command to the client. + e.clientMu.Lock() + defer e.clientMu.Unlock() + return e.client.ForceFlush(ctx) +} + +var errShutdown = fmt.Errorf("exporter is shutdown") + +// Shutdown flushes all metric data held by an exporter and releases any held +// computational resources. +func (e *exporter) Shutdown(ctx context.Context) error { + err := errShutdown + e.shutdownOnce.Do(func() { + e.clientMu.Lock() + client := e.client + e.client = shutdownClient{} + e.clientMu.Unlock() + err = client.Shutdown(ctx) + }) + return err +} + +// New constructs a new Exporter. The client is assumed to be fully started +// and able to communicate with its OTLP receiving endpoint. +func New(client Client) metric.Exporter { + return &exporter{client: client} +} + +type shutdownClient struct{} + +func (c shutdownClient) err(ctx context.Context) error { + if err := ctx.Err(); err != nil { + return err + } + return errShutdown +} + +func (c shutdownClient) UploadMetrics(ctx context.Context, _ *mpb.ResourceMetrics) error { + return c.err(ctx) +} + +func (c shutdownClient) ForceFlush(ctx context.Context) error { + return c.err(ctx) +} + +func (c shutdownClient) Shutdown(ctx context.Context) error { + return c.err(ctx) +} From 92aa3b557795630aa3b652d5dcb735d6c134e9a2 Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Fri, 12 Aug 2022 13:13:55 -0700 Subject: [PATCH 04/34] Add race detection test for Exporter --- exporters/otlp/otlpmetric/exporter_test.go | 92 ++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 exporters/otlp/otlpmetric/exporter_test.go diff --git a/exporters/otlp/otlpmetric/exporter_test.go b/exporters/otlp/otlpmetric/exporter_test.go new file mode 100644 index 00000000000..2935933c789 --- /dev/null +++ b/exporters/otlp/otlpmetric/exporter_test.go @@ -0,0 +1,92 @@ +// 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. + +//go:build go1.18 +// +build go1.18 + +package otlpmetric // import "go.opentelemetry.io/otel/exporters/otlp/otlpmetric" + +import ( + "context" + "sync" + "testing" + + "github.com/stretchr/testify/assert" + "go.opentelemetry.io/otel/sdk/metric/metricdata" + mpb "go.opentelemetry.io/proto/otlp/metrics/v1" +) + +type client struct { + // n is incremented by all Client methods. If these methods are called + // concurrently this should fail tests run with the race detector. + n int +} + +func (c client) UploadMetrics(context.Context, *mpb.ResourceMetrics) error { + c.n++ + return nil +} + +func (c client) ForceFlush(context.Context) error { + c.n++ + return nil +} + +func (c client) Shutdown(context.Context) error { + c.n++ + return nil +} + +func TestExporterClientConcurrency(t *testing.T) { + const goroutines = 5 + + exp := New(client{}) + rm := metricdata.ResourceMetrics{} + ctx := context.Background() + + done := make(chan struct{}) + first := make(chan struct{}, goroutines) + var wg sync.WaitGroup + for i := 0; i < goroutines; i++ { + wg.Add(1) + go func() { + defer wg.Done() + assert.NoError(t, exp.Export(ctx, rm)) + assert.NoError(t, exp.ForceFlush(ctx)) + // Ensure some work is done before shutting down. + first <- struct{}{} + + for { + _ = exp.Export(ctx, rm) + _ = exp.ForceFlush(ctx) + + select { + case <-done: + return + default: + } + } + }() + } + + for i := 0; i < goroutines; i++ { + <-first + } + close(first) + assert.NoError(t, exp.Shutdown(ctx)) + assert.ErrorIs(t, exp.Shutdown(ctx), errShutdown) + + close(done) + wg.Wait() +} From 023ed331508d8e25b359776adfb013179d035341 Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Fri, 12 Aug 2022 13:16:42 -0700 Subject: [PATCH 05/34] Expand New godocs --- exporters/otlp/otlpmetric/exporter.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/exporters/otlp/otlpmetric/exporter.go b/exporters/otlp/otlpmetric/exporter.go index 52b6b63b730..d2a8c0b544c 100644 --- a/exporters/otlp/otlpmetric/exporter.go +++ b/exporters/otlp/otlpmetric/exporter.go @@ -75,8 +75,9 @@ func (e *exporter) Shutdown(ctx context.Context) error { return err } -// New constructs a new Exporter. The client is assumed to be fully started -// and able to communicate with its OTLP receiving endpoint. +// New return an Exporter that uses client to transmits the OTLP data it +// produces. The client is assumed to be fully started and able to communicate +// with its OTLP receiving endpoint. func New(client Client) metric.Exporter { return &exporter{client: client} } From cefa7aba7473458e2295c03f813f5c3fa9469794 Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Fri, 12 Aug 2022 13:50:30 -0700 Subject: [PATCH 06/34] Fix lint --- exporters/otlp/otlpmetric/client.go | 4 ++-- exporters/otlp/otlpmetric/exporter.go | 2 +- exporters/otlp/otlpmetric/exporter_test.go | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/exporters/otlp/otlpmetric/client.go b/exporters/otlp/otlpmetric/client.go index 0ef59e3c371..48b0fe805e2 100644 --- a/exporters/otlp/otlpmetric/client.go +++ b/exporters/otlp/otlpmetric/client.go @@ -23,7 +23,7 @@ import ( mpb "go.opentelemetry.io/proto/otlp/metrics/v1" ) -// Client handles the transmission of OTLP data to an OTLP recieving endpoint. +// Client handles the transmission of OTLP data to an OTLP receiving endpoint. type Client interface { // UploadMetrics transmits metric data to an OTLP receiver. // @@ -45,7 +45,7 @@ type Client interface { // appropriate error should be returned in these situations. // // Shutdown will only be called once by the Exporter. Once a return value - // is recieved by the Exporter from Shutdown the Client will not be used + // is received by the Exporter from Shutdown the Client will not be used // anymore. Therefore all computational resources need to be released // after this is called so the Client can be garbage collected. Shutdown(context.Context) error diff --git a/exporters/otlp/otlpmetric/exporter.go b/exporters/otlp/otlpmetric/exporter.go index d2a8c0b544c..dc435f56800 100644 --- a/exporters/otlp/otlpmetric/exporter.go +++ b/exporters/otlp/otlpmetric/exporter.go @@ -30,7 +30,7 @@ import ( // exporter exports metrics data as OTLP. type exporter struct { - // Ensure syncronous access to the client accross all functionality. + // Ensure synchronous access to the client across all functionality. clientMu sync.Mutex client Client diff --git a/exporters/otlp/otlpmetric/exporter_test.go b/exporters/otlp/otlpmetric/exporter_test.go index 2935933c789..37468c143a6 100644 --- a/exporters/otlp/otlpmetric/exporter_test.go +++ b/exporters/otlp/otlpmetric/exporter_test.go @@ -23,6 +23,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "go.opentelemetry.io/otel/sdk/metric/metricdata" mpb "go.opentelemetry.io/proto/otlp/metrics/v1" ) From 80e7287963e4a135721be985b8606d10dd5362bc Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Tue, 16 Aug 2022 12:26:30 -0700 Subject: [PATCH 07/34] Add back the otlpmetrichttp pkg from main --- .../otlpmetrichttp/certificate_test.go | 92 ++++ .../otlp/otlpmetric/otlpmetrichttp/client.go | 300 ++++++++++++ .../otlpmetric/otlpmetrichttp/client_test.go | 271 +++++++++++ .../otlpmetrichttp/client_unit_test.go | 68 +++ .../otlp/otlpmetric/otlpmetrichttp/doc.go | 23 + .../otlpmetric/otlpmetrichttp/exporter.go | 31 ++ .../otlp/otlpmetric/otlpmetrichttp/go.mod | 46 ++ .../otlp/otlpmetric/otlpmetrichttp/go.sum | 435 ++++++++++++++++++ .../otlpmetrichttp/mock_collector_test.go | 239 ++++++++++ .../otlp/otlpmetric/otlpmetrichttp/options.go | 185 ++++++++ 10 files changed, 1690 insertions(+) create mode 100644 exporters/otlp/otlpmetric/otlpmetrichttp/certificate_test.go create mode 100644 exporters/otlp/otlpmetric/otlpmetrichttp/client.go create mode 100644 exporters/otlp/otlpmetric/otlpmetrichttp/client_test.go create mode 100644 exporters/otlp/otlpmetric/otlpmetrichttp/client_unit_test.go create mode 100644 exporters/otlp/otlpmetric/otlpmetrichttp/doc.go create mode 100644 exporters/otlp/otlpmetric/otlpmetrichttp/exporter.go create mode 100644 exporters/otlp/otlpmetric/otlpmetrichttp/go.mod create mode 100644 exporters/otlp/otlpmetric/otlpmetrichttp/go.sum create mode 100644 exporters/otlp/otlpmetric/otlpmetrichttp/mock_collector_test.go create mode 100644 exporters/otlp/otlpmetric/otlpmetrichttp/options.go diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/certificate_test.go b/exporters/otlp/otlpmetric/otlpmetrichttp/certificate_test.go new file mode 100644 index 00000000000..d75547f6e4c --- /dev/null +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/certificate_test.go @@ -0,0 +1,92 @@ +// 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 otlpmetrichttp_test + +import ( + "bytes" + "crypto/ecdsa" + "crypto/elliptic" + cryptorand "crypto/rand" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "math/big" + mathrand "math/rand" + "net" + "time" +) + +type mathRandReader struct{} + +func (mathRandReader) Read(p []byte) (n int, err error) { + return mathrand.Read(p) +} + +var randReader mathRandReader + +type pemCertificate struct { + Certificate []byte + PrivateKey []byte +} + +// Based on https://golang.org/src/crypto/tls/generate_cert.go, +// simplified and weakened. +func generateWeakCertificate() (*pemCertificate, error) { + priv, err := ecdsa.GenerateKey(elliptic.P256(), randReader) + if err != nil { + return nil, err + } + keyUsage := x509.KeyUsageDigitalSignature + notBefore := time.Now() + notAfter := notBefore.Add(time.Hour) + serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) + serialNumber, err := cryptorand.Int(randReader, serialNumberLimit) + if err != nil { + return nil, err + } + template := x509.Certificate{ + SerialNumber: serialNumber, + Subject: pkix.Name{ + Organization: []string{"otel-go"}, + }, + NotBefore: notBefore, + NotAfter: notAfter, + KeyUsage: keyUsage, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + DNSNames: []string{"localhost"}, + IPAddresses: []net.IP{net.IPv6loopback, net.IPv4(127, 0, 0, 1)}, + } + derBytes, err := x509.CreateCertificate(randReader, &template, &template, &priv.PublicKey, priv) + if err != nil { + return nil, err + } + certificateBuffer := new(bytes.Buffer) + if err := pem.Encode(certificateBuffer, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil { + return nil, err + } + privDERBytes, err := x509.MarshalPKCS8PrivateKey(priv) + if err != nil { + return nil, err + } + privBuffer := new(bytes.Buffer) + if err := pem.Encode(privBuffer, &pem.Block{Type: "PRIVATE KEY", Bytes: privDERBytes}); err != nil { + return nil, err + } + return &pemCertificate{ + Certificate: certificateBuffer.Bytes(), + PrivateKey: privBuffer.Bytes(), + }, nil +} diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/client.go b/exporters/otlp/otlpmetric/otlpmetrichttp/client.go new file mode 100644 index 00000000000..766bcf48744 --- /dev/null +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/client.go @@ -0,0 +1,300 @@ +// 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 otlpmetrichttp // import "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp" + +import ( + "bytes" + "compress/gzip" + "context" + "fmt" + "io" + "net" + "net/http" + "net/url" + "strconv" + "sync" + "time" + + "google.golang.org/protobuf/proto" + + "go.opentelemetry.io/otel/exporters/otlp/internal/retry" + "go.opentelemetry.io/otel/exporters/otlp/otlpmetric" + "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/internal/otlpconfig" + colmetricpb "go.opentelemetry.io/proto/otlp/collector/metrics/v1" + metricpb "go.opentelemetry.io/proto/otlp/metrics/v1" +) + +const contentTypeProto = "application/x-protobuf" + +var gzPool = sync.Pool{ + New: func() interface{} { + w := gzip.NewWriter(io.Discard) + return w + }, +} + +// Keep it in sync with golang's DefaultTransport from net/http! We +// have our own copy to avoid handling a situation where the +// DefaultTransport is overwritten with some different implementation +// of http.RoundTripper or it's modified by other package. +var ourTransport = &http.Transport{ + Proxy: http.ProxyFromEnvironment, + DialContext: (&net.Dialer{ + Timeout: 30 * time.Second, + KeepAlive: 30 * time.Second, + }).DialContext, + ForceAttemptHTTP2: true, + MaxIdleConns: 100, + IdleConnTimeout: 90 * time.Second, + TLSHandshakeTimeout: 10 * time.Second, + ExpectContinueTimeout: 1 * time.Second, +} + +type client struct { + name string + cfg otlpconfig.SignalConfig + generalCfg otlpconfig.Config + requestFunc retry.RequestFunc + client *http.Client + stopCh chan struct{} + stopOnce sync.Once +} + +// NewClient creates a new HTTP metric client. +func NewClient(opts ...Option) otlpmetric.Client { + cfg := otlpconfig.NewHTTPConfig(asHTTPOptions(opts)...) + + httpClient := &http.Client{ + Transport: ourTransport, + Timeout: cfg.Metrics.Timeout, + } + if cfg.Metrics.TLSCfg != nil { + transport := ourTransport.Clone() + transport.TLSClientConfig = cfg.Metrics.TLSCfg + httpClient.Transport = transport + } + + stopCh := make(chan struct{}) + return &client{ + name: "metrics", + cfg: cfg.Metrics, + generalCfg: cfg, + requestFunc: cfg.RetryConfig.RequestFunc(evaluate), + stopCh: stopCh, + client: httpClient, + } +} + +// Start does nothing in a HTTP client. +func (d *client) Start(ctx context.Context) error { + // nothing to do + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + return nil +} + +// Stop shuts down the client and interrupt any in-flight request. +func (d *client) Stop(ctx context.Context) error { + d.stopOnce.Do(func() { + close(d.stopCh) + }) + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + return nil +} + +// UploadMetrics sends a batch of metrics to the collector. +func (d *client) UploadMetrics(ctx context.Context, protoMetrics *metricpb.ResourceMetrics) error { + pbRequest := &colmetricpb.ExportMetricsServiceRequest{ + ResourceMetrics: []*metricpb.ResourceMetrics{protoMetrics}, + } + rawRequest, err := proto.Marshal(pbRequest) + if err != nil { + return err + } + + ctx, cancel := d.contextWithStop(ctx) + defer cancel() + + request, err := d.newRequest(rawRequest) + if err != nil { + return err + } + + return d.requestFunc(ctx, func(ctx context.Context) error { + select { + case <-ctx.Done(): + return ctx.Err() + default: + } + + request.reset(ctx) + resp, err := d.client.Do(request.Request) + if err != nil { + return err + } + + var rErr error + switch resp.StatusCode { + case http.StatusOK: + // Success, do not retry. + case http.StatusTooManyRequests, + http.StatusServiceUnavailable: + // Retry-able failure. + rErr = newResponseError(resp.Header) + + // Going to retry, drain the body to reuse the connection. + if _, err := io.Copy(io.Discard, resp.Body); err != nil { + _ = resp.Body.Close() + return err + } + default: + rErr = fmt.Errorf("failed to send %s to %s: %s", d.name, request.URL, resp.Status) + } + + if err := resp.Body.Close(); err != nil { + return err + } + return rErr + }) +} + +func (d *client) newRequest(body []byte) (request, error) { + u := url.URL{Scheme: d.getScheme(), Host: d.cfg.Endpoint, Path: d.cfg.URLPath} + r, err := http.NewRequest(http.MethodPost, u.String(), nil) + if err != nil { + return request{Request: r}, err + } + + for k, v := range d.cfg.Headers { + r.Header.Set(k, v) + } + r.Header.Set("Content-Type", contentTypeProto) + + req := request{Request: r} + switch Compression(d.cfg.Compression) { + case NoCompression: + r.ContentLength = (int64)(len(body)) + req.bodyReader = bodyReader(body) + case GzipCompression: + // Ensure the content length is not used. + r.ContentLength = -1 + r.Header.Set("Content-Encoding", "gzip") + + gz := gzPool.Get().(*gzip.Writer) + defer gzPool.Put(gz) + + var b bytes.Buffer + gz.Reset(&b) + + if _, err := gz.Write(body); err != nil { + return req, err + } + // Close needs to be called to ensure body if fully written. + if err := gz.Close(); err != nil { + return req, err + } + + req.bodyReader = bodyReader(b.Bytes()) + } + + return req, nil +} + +// bodyReader returns a closure returning a new reader for buf. +func bodyReader(buf []byte) func() io.ReadCloser { + return func() io.ReadCloser { + return io.NopCloser(bytes.NewReader(buf)) + } +} + +// request wraps an http.Request with a resettable body reader. +type request struct { + *http.Request + + // bodyReader allows the same body to be used for multiple requests. + bodyReader func() io.ReadCloser +} + +// reset reinitializes the request Body and uses ctx for the request. +func (r *request) reset(ctx context.Context) { + r.Body = r.bodyReader() + r.Request = r.Request.WithContext(ctx) +} + +// retryableError represents a request failure that can be retried. +type retryableError struct { + throttle int64 +} + +// newResponseError returns a retryableError and will extract any explicit +// throttle delay contained in headers. +func newResponseError(header http.Header) error { + var rErr retryableError + if s, ok := header["Retry-After"]; ok { + if t, err := strconv.ParseInt(s[0], 10, 64); err == nil { + rErr.throttle = t + } + } + return rErr +} + +func (e retryableError) Error() string { + return "retry-able request failure" +} + +// evaluate returns if err is retry-able. If it is and it includes an explicit +// throttling delay, that delay is also returned. +func evaluate(err error) (bool, time.Duration) { + if err == nil { + return false, 0 + } + + rErr, ok := err.(retryableError) + if !ok { + return false, 0 + } + + return true, time.Duration(rErr.throttle) +} + +func (d *client) getScheme() string { + if d.cfg.Insecure { + return "http" + } + return "https" +} + +func (d *client) contextWithStop(ctx context.Context) (context.Context, context.CancelFunc) { + // Unify the parent context Done signal with the client's stop + // channel. + ctx, cancel := context.WithCancel(ctx) + go func(ctx context.Context, cancel context.CancelFunc) { + select { + case <-ctx.Done(): + // Nothing to do, either cancelled or deadline + // happened. + case <-d.stopCh: + cancel() + } + }(ctx, cancel) + return ctx, cancel +} diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/client_test.go b/exporters/otlp/otlpmetric/otlpmetrichttp/client_test.go new file mode 100644 index 00000000000..5e614da2640 --- /dev/null +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/client_test.go @@ -0,0 +1,271 @@ +// 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 otlpmetrichttp_test + +import ( + "context" + "net/http" + "os" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "go.opentelemetry.io/otel/exporters/otlp/otlpmetric" + "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/internal/otlpmetrictest" + "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp" + "go.opentelemetry.io/otel/sdk/resource" +) + +const ( + relOtherMetricsPath = "post/metrics/here" + otherMetricsPath = "/post/metrics/here" +) + +var ( + oneRecord = otlpmetrictest.OneRecordReader() + + testResource = resource.Empty() +) + +var ( + testHeaders = map[string]string{ + "Otel-Go-Key-1": "somevalue", + "Otel-Go-Key-2": "someothervalue", + } +) + +func TestEndToEnd(t *testing.T) { + tests := []struct { + name string + opts []otlpmetrichttp.Option + mcCfg mockCollectorConfig + tls bool + }{ + { + name: "no extra options", + opts: nil, + }, + { + name: "with gzip compression", + opts: []otlpmetrichttp.Option{ + otlpmetrichttp.WithCompression(otlpmetrichttp.GzipCompression), + }, + }, + { + name: "with empty paths (forced to defaults)", + opts: []otlpmetrichttp.Option{ + otlpmetrichttp.WithURLPath(""), + }, + }, + { + name: "with relative paths", + opts: []otlpmetrichttp.Option{ + otlpmetrichttp.WithURLPath(relOtherMetricsPath), + }, + mcCfg: mockCollectorConfig{ + MetricsURLPath: otherMetricsPath, + }, + }, + { + name: "with TLS", + opts: nil, + mcCfg: mockCollectorConfig{ + WithTLS: true, + }, + tls: true, + }, + { + name: "with extra headers", + opts: []otlpmetrichttp.Option{ + otlpmetrichttp.WithHeaders(testHeaders), + }, + mcCfg: mockCollectorConfig{ + ExpectedHeaders: testHeaders, + }, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + mc := runMockCollector(t, tc.mcCfg) + defer mc.MustStop(t) + allOpts := []otlpmetrichttp.Option{ + otlpmetrichttp.WithEndpoint(mc.Endpoint()), + } + if tc.tls { + tlsConfig := mc.ClientTLSConfig() + require.NotNil(t, tlsConfig) + allOpts = append(allOpts, otlpmetrichttp.WithTLSClientConfig(tlsConfig)) + } else { + allOpts = append(allOpts, otlpmetrichttp.WithInsecure()) + } + allOpts = append(allOpts, tc.opts...) + client := otlpmetrichttp.NewClient(allOpts...) + ctx := context.Background() + exporter, err := otlpmetric.New(ctx, client) + if assert.NoError(t, err) { + defer func() { + assert.NoError(t, exporter.Shutdown(ctx)) + }() + otlpmetrictest.RunEndToEndTest(ctx, t, exporter, mc) + } + }) + } +} + +func TestExporterShutdown(t *testing.T) { + mc := runMockCollector(t, mockCollectorConfig{}) + defer func() { + _ = mc.Stop() + }() + + <-time.After(5 * time.Millisecond) + + otlpmetrictest.RunExporterShutdownTest(t, func() otlpmetric.Client { + return otlpmetrichttp.NewClient( + otlpmetrichttp.WithInsecure(), + otlpmetrichttp.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 := otlpmetrichttp.NewClient( + otlpmetrichttp.WithEndpoint(mc.Endpoint()), + otlpmetrichttp.WithInsecure(), + otlpmetrichttp.WithTimeout(time.Nanosecond), + ) + ctx := context.Background() + exporter, err := otlpmetric.New(ctx, client) + require.NoError(t, err) + defer func() { + assert.NoError(t, exporter.Shutdown(ctx)) + }() + err = exporter.Export(ctx, testResource, oneRecord) + assert.Equalf(t, true, os.IsTimeout(err), "expected timeout error, got: %v", err) +} + +func TestEmptyData(t *testing.T) { + mcCfg := mockCollectorConfig{} + mc := runMockCollector(t, mcCfg) + defer mc.MustStop(t) + driver := otlpmetrichttp.NewClient( + otlpmetrichttp.WithEndpoint(mc.Endpoint()), + otlpmetrichttp.WithInsecure(), + ) + ctx := context.Background() + exporter, err := otlpmetric.New(ctx, driver) + require.NoError(t, err) + defer func() { + assert.NoError(t, exporter.Shutdown(ctx)) + }() + assert.NoError(t, err) + err = exporter.Export(ctx, testResource, oneRecord) + assert.NoError(t, err) + assert.NotEmpty(t, mc.GetMetrics()) +} + +func TestCancelledContext(t *testing.T) { + statuses := []int{ + http.StatusBadRequest, + } + mcCfg := mockCollectorConfig{ + InjectHTTPStatus: statuses, + } + mc := runMockCollector(t, mcCfg) + defer mc.MustStop(t) + driver := otlpmetrichttp.NewClient( + otlpmetrichttp.WithEndpoint(mc.Endpoint()), + otlpmetrichttp.WithInsecure(), + ) + ctx, cancel := context.WithCancel(context.Background()) + exporter, err := otlpmetric.New(ctx, driver) + require.NoError(t, err) + defer func() { + assert.NoError(t, exporter.Shutdown(context.Background())) + }() + cancel() + _ = exporter.Export(ctx, testResource, oneRecord) + assert.Empty(t, mc.GetMetrics()) +} + +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 := otlpmetrichttp.NewClient( + otlpmetrichttp.WithEndpoint(mc.Endpoint()), + otlpmetrichttp.WithInsecure(), + otlpmetrichttp.WithBackoff(time.Minute), + ) + ctx := context.Background() + exporter, err := otlpmetric.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.Export(ctx, testResource, oneRecord) + assert.Error(t, err) + assert.Empty(t, mc.GetMetrics()) +} + +func TestStopWhileExporting(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 := otlpmetrichttp.NewClient( + otlpmetrichttp.WithEndpoint(mc.Endpoint()), + otlpmetrichttp.WithInsecure(), + otlpmetrichttp.WithBackoff(time.Minute), + ) + ctx := context.Background() + exporter, err := otlpmetric.New(ctx, driver) + require.NoError(t, err) + defer func() { + assert.NoError(t, exporter.Shutdown(ctx)) + }() + doneCh := make(chan struct{}) + go func() { + err := exporter.Export(ctx, testResource, oneRecord) + assert.Error(t, err) + assert.Empty(t, mc.GetMetrics()) + close(doneCh) + }() + <-time.After(time.Second) + err = exporter.Shutdown(ctx) + assert.NoError(t, err) + <-doneCh +} diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/client_unit_test.go b/exporters/otlp/otlpmetric/otlpmetrichttp/client_unit_test.go new file mode 100644 index 00000000000..4ba01c85e5e --- /dev/null +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/client_unit_test.go @@ -0,0 +1,68 @@ +// 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 otlpmetrichttp + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestUnreasonableBackoff(t *testing.T) { + cIface := NewClient( + WithEndpoint("http://localhost"), + WithInsecure(), + WithBackoff(-time.Microsecond), + ) + require.IsType(t, &client{}, cIface) + c := cIface.(*client) + assert.True(t, c.generalCfg.RetryConfig.Enabled) + assert.Equal(t, 5*time.Second, c.generalCfg.RetryConfig.InitialInterval) + assert.Equal(t, 300*time.Millisecond, c.generalCfg.RetryConfig.MaxInterval) + assert.Equal(t, time.Minute, c.generalCfg.RetryConfig.MaxElapsedTime) +} + +func TestUnreasonableMaxAttempts(t *testing.T) { + type testcase struct { + name string + maxAttempts int + } + for _, tc := range []testcase{ + { + name: "negative max attempts", + maxAttempts: -3, + }, + { + name: "too large max attempts", + maxAttempts: 10, + }, + } { + t.Run(tc.name, func(t *testing.T) { + cIface := NewClient( + WithEndpoint("http://localhost"), + WithInsecure(), + WithMaxAttempts(tc.maxAttempts), + ) + require.IsType(t, &client{}, cIface) + c := cIface.(*client) + assert.True(t, c.generalCfg.RetryConfig.Enabled) + assert.Equal(t, 5*time.Second, c.generalCfg.RetryConfig.InitialInterval) + assert.Equal(t, 30*time.Second, c.generalCfg.RetryConfig.MaxInterval) + assert.Equal(t, 145*time.Second, c.generalCfg.RetryConfig.MaxElapsedTime) + }) + } +} diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/doc.go b/exporters/otlp/otlpmetric/otlpmetrichttp/doc.go new file mode 100644 index 00000000000..d096388320d --- /dev/null +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/doc.go @@ -0,0 +1,23 @@ +// 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 otlpmetrichttp provides a client that sends metrics to the collector +using HTTP with binary protobuf payloads. + +This package is currently in a pre-GA phase. Backwards incompatible changes +may be introduced in subsequent minor version releases as we work to track the +evolving OpenTelemetry specification and user feedback. +*/ +package otlpmetrichttp // import "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp" diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/exporter.go b/exporters/otlp/otlpmetric/otlpmetrichttp/exporter.go new file mode 100644 index 00000000000..de09e7cdcaa --- /dev/null +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/exporter.go @@ -0,0 +1,31 @@ +// 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 otlpmetrichttp // import "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp" + +import ( + "context" + + "go.opentelemetry.io/otel/exporters/otlp/otlpmetric" +) + +// New constructs a new Exporter and starts it. +func New(ctx context.Context, opts ...Option) (*otlpmetric.Exporter, error) { + return otlpmetric.New(ctx, NewClient(opts...)) +} + +// NewUnstarted constructs a new Exporter and does not start it. +func NewUnstarted(opts ...Option) *otlpmetric.Exporter { + return otlpmetric.NewUnstarted(NewClient(opts...)) +} diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/go.mod b/exporters/otlp/otlpmetric/otlpmetrichttp/go.mod new file mode 100644 index 00000000000..6ac02fa0491 --- /dev/null +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/go.mod @@ -0,0 +1,46 @@ +module go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp + +go 1.17 + +require ( + github.com/stretchr/testify v1.7.1 + go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.9.0 + go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.31.0 + go.opentelemetry.io/otel/sdk v1.9.0 + go.opentelemetry.io/proto/otlp v0.18.0 + google.golang.org/protobuf v1.28.0 +) + +require ( + github.com/cenkalti/backoff/v4 v4.1.3 // indirect + github.com/davecgh/go-spew v1.1.0 // indirect + github.com/go-logr/logr v1.2.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/golang/protobuf v1.5.2 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + go.opentelemetry.io/otel v1.9.0 // indirect + go.opentelemetry.io/otel/metric v0.31.0 // indirect + go.opentelemetry.io/otel/sdk/metric v0.31.0 // indirect + go.opentelemetry.io/otel/trace v1.9.0 // indirect + golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 // indirect + golang.org/x/sys v0.0.0-20210510120138-977fb7262007 // indirect + golang.org/x/text v0.3.5 // indirect + google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1 // indirect + google.golang.org/grpc v1.46.2 // indirect + gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect +) + +replace go.opentelemetry.io/otel => ../../../.. + +replace go.opentelemetry.io/otel/sdk => ../../../../sdk + +replace go.opentelemetry.io/otel/sdk/metric => ../../../../sdk/metric + +replace go.opentelemetry.io/otel/exporters/otlp/otlpmetric => ../ + +replace go.opentelemetry.io/otel/metric => ../../../../metric + +replace go.opentelemetry.io/otel/trace => ../../../../trace + +replace go.opentelemetry.io/otel/exporters/otlp/internal/retry => ../../internal/retry diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/go.sum b/exporters/otlp/otlpmetric/otlpmetrichttp/go.sum new file mode 100644 index 00000000000..aa3f8bac4ee --- /dev/null +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/go.sum @@ -0,0 +1,435 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= +github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4= +github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= +github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ= +github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 h1:BZHcxBETFHIdVyhyEfOvn/RdU/QGdLI4y34qQGjGWO0= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.opentelemetry.io/proto/otlp v0.18.0 h1:W5hyXNComRa23tGpKwG+FRAc4rfF6ZUg1JReK+QHS80= +go.opentelemetry.io/proto/otlp v0.18.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1 h1:b9mVrqYfq3P4bCdaLg1qtBnPzUYgglsIdjZkL/fQVOE= +google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.46.2 h1:u+MLGgVf7vRdjEYZ8wDFhAVNmhkbJ5hmrA1LMWK1CAQ= +google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/mock_collector_test.go b/exporters/otlp/otlpmetric/otlpmetrichttp/mock_collector_test.go new file mode 100644 index 00000000000..5776c67a016 --- /dev/null +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/mock_collector_test.go @@ -0,0 +1,239 @@ +// 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 otlpmetrichttp_test + +import ( + "bytes" + "compress/gzip" + "context" + "crypto/tls" + "fmt" + "io" + "net" + "net/http" + "sync" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" + + "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/internal/otlpconfig" + "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/internal/otlpmetrictest" + collectormetricpb "go.opentelemetry.io/proto/otlp/collector/metrics/v1" + metricpb "go.opentelemetry.io/proto/otlp/metrics/v1" +) + +type mockCollector struct { + endpoint string + server *http.Server + + spanLock sync.Mutex + metricsStorage otlpmetrictest.MetricsStorage + + injectHTTPStatus []int + injectContentType string + delay <-chan struct{} + + clientTLSConfig *tls.Config + expectedHeaders map[string]string +} + +func (c *mockCollector) Stop() error { + return c.server.Shutdown(context.Background()) +} + +func (c *mockCollector) MustStop(t *testing.T) { + assert.NoError(t, c.server.Shutdown(context.Background())) +} + +func (c *mockCollector) GetMetrics() []*metricpb.Metric { + c.spanLock.Lock() + defer c.spanLock.Unlock() + return c.metricsStorage.GetMetrics() +} + +func (c *mockCollector) Endpoint() string { + return c.endpoint +} + +func (c *mockCollector) ClientTLSConfig() *tls.Config { + return c.clientTLSConfig +} + +func (c *mockCollector) serveMetrics(w http.ResponseWriter, r *http.Request) { + if c.delay != nil { + select { + case <-c.delay: + case <-r.Context().Done(): + return + } + } + + if !c.checkHeaders(r) { + w.WriteHeader(http.StatusBadRequest) + return + } + response := collectormetricpb.ExportMetricsServiceResponse{} + rawResponse, err := proto.Marshal(&response) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + if injectedStatus := c.getInjectHTTPStatus(); injectedStatus != 0 { + writeReply(w, rawResponse, injectedStatus, c.injectContentType) + return + } + rawRequest, err := readRequest(r) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + + request, err := unmarshalMetricsRequest(rawRequest, r.Header.Get("content-type")) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + return + } + writeReply(w, rawResponse, 0, c.injectContentType) + c.spanLock.Lock() + defer c.spanLock.Unlock() + c.metricsStorage.AddMetrics(request) +} + +func unmarshalMetricsRequest(rawRequest []byte, contentType string) (*collectormetricpb.ExportMetricsServiceRequest, error) { + request := &collectormetricpb.ExportMetricsServiceRequest{} + if contentType != "application/x-protobuf" { + return request, fmt.Errorf("invalid content-type: %s, only application/x-protobuf is supported", contentType) + } + err := proto.Unmarshal(rawRequest, request) + return request, err +} + +func (c *mockCollector) checkHeaders(r *http.Request) bool { + for k, v := range c.expectedHeaders { + got := r.Header.Get(k) + if got != v { + return false + } + } + return true +} + +func (c *mockCollector) getInjectHTTPStatus() int { + if len(c.injectHTTPStatus) == 0 { + return 0 + } + status := c.injectHTTPStatus[0] + c.injectHTTPStatus = c.injectHTTPStatus[1:] + if len(c.injectHTTPStatus) == 0 { + c.injectHTTPStatus = nil + } + return status +} + +func readRequest(r *http.Request) ([]byte, error) { + if r.Header.Get("Content-Encoding") == "gzip" { + return readGzipBody(r.Body) + } + return io.ReadAll(r.Body) +} + +func readGzipBody(body io.Reader) ([]byte, error) { + rawRequest := bytes.Buffer{} + gunzipper, err := gzip.NewReader(body) + if err != nil { + return nil, err + } + defer gunzipper.Close() + _, err = io.Copy(&rawRequest, gunzipper) + if err != nil { + return nil, err + } + return rawRequest.Bytes(), nil +} + +func writeReply(w http.ResponseWriter, rawResponse []byte, injectHTTPStatus int, injectContentType string) { + status := http.StatusOK + if injectHTTPStatus != 0 { + status = injectHTTPStatus + } + contentType := "application/x-protobuf" + if injectContentType != "" { + contentType = injectContentType + } + w.Header().Set("Content-Type", contentType) + w.WriteHeader(status) + _, _ = w.Write(rawResponse) +} + +type mockCollectorConfig struct { + MetricsURLPath string + Port int + InjectHTTPStatus []int + InjectContentType string + Delay <-chan struct{} + WithTLS bool + ExpectedHeaders map[string]string +} + +func (c *mockCollectorConfig) fillInDefaults() { + if c.MetricsURLPath == "" { + c.MetricsURLPath = otlpconfig.DefaultMetricsPath + } +} + +func runMockCollector(t *testing.T, cfg mockCollectorConfig) *mockCollector { + cfg.fillInDefaults() + ln, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", cfg.Port)) + require.NoError(t, err) + _, portStr, err := net.SplitHostPort(ln.Addr().String()) + require.NoError(t, err) + m := &mockCollector{ + endpoint: fmt.Sprintf("localhost:%s", portStr), + metricsStorage: otlpmetrictest.NewMetricsStorage(), + injectHTTPStatus: cfg.InjectHTTPStatus, + injectContentType: cfg.InjectContentType, + delay: cfg.Delay, + expectedHeaders: cfg.ExpectedHeaders, + } + mux := http.NewServeMux() + mux.Handle(cfg.MetricsURLPath, http.HandlerFunc(m.serveMetrics)) + server := &http.Server{ + Handler: mux, + } + if cfg.WithTLS { + pem, err := generateWeakCertificate() + require.NoError(t, err) + tlsCertificate, err := tls.X509KeyPair(pem.Certificate, pem.PrivateKey) + require.NoError(t, err) + server.TLSConfig = &tls.Config{ + Certificates: []tls.Certificate{tlsCertificate}, + } + + m.clientTLSConfig = &tls.Config{ + InsecureSkipVerify: true, + } + } + go func() { + if cfg.WithTLS { + _ = server.ServeTLS(ln, "", "") + } else { + _ = server.Serve(ln) + } + }() + m.server = server + return m +} diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/options.go b/exporters/otlp/otlpmetric/otlpmetrichttp/options.go new file mode 100644 index 00000000000..8d12c791954 --- /dev/null +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/options.go @@ -0,0 +1,185 @@ +// 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 otlpmetrichttp // import "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp" + +import ( + "crypto/tls" + "time" + + "go.opentelemetry.io/otel/exporters/otlp/internal/retry" + "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/internal/otlpconfig" +) + +// Compression describes the compression used for payloads sent to the +// collector. +type Compression otlpconfig.Compression + +const ( + // NoCompression tells the driver to send payloads without + // compression. + NoCompression = Compression(otlpconfig.NoCompression) + // GzipCompression tells the driver to send payloads after + // compressing them with gzip. + GzipCompression = Compression(otlpconfig.GzipCompression) +) + +// Option applies an option to the HTTP client. +type Option interface { + applyHTTPOption(otlpconfig.Config) otlpconfig.Config +} + +func asHTTPOptions(opts []Option) []otlpconfig.HTTPOption { + converted := make([]otlpconfig.HTTPOption, len(opts)) + for i, o := range opts { + converted[i] = otlpconfig.NewHTTPOption(o.applyHTTPOption) + } + return converted +} + +// RetryConfig defines configuration for retrying batches in case of export +// failure using an exponential backoff. +type RetryConfig retry.Config + +type wrappedOption struct { + otlpconfig.HTTPOption +} + +func (w wrappedOption) applyHTTPOption(cfg otlpconfig.Config) otlpconfig.Config { + return w.ApplyHTTPOption(cfg) +} + +// WithEndpoint allows one to set the address of the collector endpoint that +// the driver will use to send metrics. If unset, it will instead try to use +// the default endpoint (localhost:4318). Note that the endpoint must not +// contain any URL path. +func WithEndpoint(endpoint string) Option { + return wrappedOption{otlpconfig.WithEndpoint(endpoint)} +} + +// WithCompression tells the driver to compress the sent data. +func WithCompression(compression Compression) Option { + return wrappedOption{otlpconfig.WithCompression(otlpconfig.Compression(compression))} +} + +// WithURLPath allows one to override the default URL path used +// for sending metrics. If unset, default ("/v1/metrics") will be used. +func WithURLPath(urlPath string) Option { + return wrappedOption{otlpconfig.WithURLPath(urlPath)} +} + +// WithMaxAttempts allows one to override how many times the driver +// will try to send the payload in case of retryable errors. +// The max attempts is limited to at most 5 retries. If unset, +// default (5) will be used. +// +// Deprecated: Use WithRetry instead. +func WithMaxAttempts(maxAttempts int) Option { + if maxAttempts > 5 || maxAttempts < 0 { + maxAttempts = 5 + } + return wrappedOption{ + otlpconfig.NewHTTPOption(func(cfg otlpconfig.Config) otlpconfig.Config { + cfg.RetryConfig.Enabled = true + + var ( + init = cfg.RetryConfig.InitialInterval + maxI = cfg.RetryConfig.MaxInterval + maxE = cfg.RetryConfig.MaxElapsedTime + ) + + if init == 0 { + init = retry.DefaultConfig.InitialInterval + } + if maxI == 0 { + maxI = retry.DefaultConfig.MaxInterval + } + if maxE == 0 { + maxE = retry.DefaultConfig.MaxElapsedTime + } + attempts := int64(maxE+init) / int64(maxI) + + if int64(maxAttempts) == attempts { + return cfg + } + + maxE = time.Duration(int64(maxAttempts)*int64(maxI)) - init + + cfg.RetryConfig.InitialInterval = init + cfg.RetryConfig.MaxInterval = maxI + cfg.RetryConfig.MaxElapsedTime = maxE + + return cfg + }), + } +} + +// WithBackoff tells the driver to use the duration as a base of the +// exponential backoff strategy. If unset, default (300ms) will be +// used. +// +// Deprecated: Use WithRetry instead. +func WithBackoff(duration time.Duration) Option { + if duration < 0 { + duration = 300 * time.Millisecond + } + return wrappedOption{ + otlpconfig.NewHTTPOption(func(cfg otlpconfig.Config) otlpconfig.Config { + cfg.RetryConfig.Enabled = true + cfg.RetryConfig.MaxInterval = duration + if cfg.RetryConfig.InitialInterval == 0 { + cfg.RetryConfig.InitialInterval = retry.DefaultConfig.InitialInterval + } + if cfg.RetryConfig.MaxElapsedTime == 0 { + cfg.RetryConfig.MaxElapsedTime = retry.DefaultConfig.MaxElapsedTime + } + return cfg + }), + } +} + +// WithTLSClientConfig can be used to set up a custom TLS +// configuration for the client used to send payloads to the +// collector. Use it if you want to use a custom certificate. +func WithTLSClientConfig(tlsCfg *tls.Config) Option { + return wrappedOption{otlpconfig.WithTLSClientConfig(tlsCfg)} +} + +// WithInsecure tells the driver to connect to the collector using the +// HTTP scheme, instead of HTTPS. +func WithInsecure() Option { + return wrappedOption{otlpconfig.WithInsecure()} +} + +// WithHeaders allows one to tell the driver to send additional HTTP +// headers with the payloads. Specifying headers like Content-Length, +// Content-Encoding and Content-Type may result in a broken driver. +func WithHeaders(headers map[string]string) Option { + return wrappedOption{otlpconfig.WithHeaders(headers)} +} + +// WithTimeout tells the driver the max waiting time for the backend to process +// each metrics batch. If unset, the default will be 10 seconds. +func WithTimeout(duration time.Duration) Option { + return wrappedOption{otlpconfig.WithTimeout(duration)} +} + +// WithRetry configures the retry policy for transient errors that may occurs +// when exporting traces. An exponential back-off algorithm is used to ensure +// endpoints are not overwhelmed with retries. If unset, the default retry +// policy will retry after 5 seconds and increase exponentially after each +// error for a total of 1 minute. +func WithRetry(rc RetryConfig) Option { + return wrappedOption{otlpconfig.WithRetry(retry.Config(rc))} +} From 1a03ee1e3c9a24e1a69b1d6061f005e09a526bc6 Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Tue, 16 Aug 2022 12:27:59 -0700 Subject: [PATCH 08/34] Restrict to Go 1.18 and above --- exporters/otlp/otlpmetric/otlpmetrichttp/certificate_test.go | 3 +++ exporters/otlp/otlpmetric/otlpmetrichttp/client.go | 3 +++ exporters/otlp/otlpmetric/otlpmetrichttp/client_test.go | 3 +++ exporters/otlp/otlpmetric/otlpmetrichttp/client_unit_test.go | 3 +++ exporters/otlp/otlpmetric/otlpmetrichttp/exporter.go | 3 +++ exporters/otlp/otlpmetric/otlpmetrichttp/go.mod | 2 +- .../otlp/otlpmetric/otlpmetrichttp/mock_collector_test.go | 3 +++ exporters/otlp/otlpmetric/otlpmetrichttp/options.go | 3 +++ 8 files changed, 22 insertions(+), 1 deletion(-) diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/certificate_test.go b/exporters/otlp/otlpmetric/otlpmetrichttp/certificate_test.go index d75547f6e4c..eb33e6e54a0 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/certificate_test.go +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/certificate_test.go @@ -12,6 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +//go:build go1.18 +// +build go1.18 + package otlpmetrichttp_test import ( diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/client.go b/exporters/otlp/otlpmetric/otlpmetrichttp/client.go index 766bcf48744..3f12413be9d 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/client.go +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/client.go @@ -12,6 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +//go:build go1.18 +// +build go1.18 + package otlpmetrichttp // import "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp" import ( diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/client_test.go b/exporters/otlp/otlpmetric/otlpmetrichttp/client_test.go index 5e614da2640..4c8bc1bfcc4 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/client_test.go +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/client_test.go @@ -12,6 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +//go:build go1.18 +// +build go1.18 + package otlpmetrichttp_test import ( diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/client_unit_test.go b/exporters/otlp/otlpmetric/otlpmetrichttp/client_unit_test.go index 4ba01c85e5e..fd6dadecb89 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/client_unit_test.go +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/client_unit_test.go @@ -12,6 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +//go:build go1.18 +// +build go1.18 + package otlpmetrichttp import ( diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/exporter.go b/exporters/otlp/otlpmetric/otlpmetrichttp/exporter.go index de09e7cdcaa..faa4ba79198 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/exporter.go +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/exporter.go @@ -12,6 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +//go:build go1.18 +// +build go1.18 + package otlpmetrichttp // import "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp" import ( diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/go.mod b/exporters/otlp/otlpmetric/otlpmetrichttp/go.mod index 6ac02fa0491..ae07225ca34 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/go.mod +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/go.mod @@ -1,6 +1,6 @@ module go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp -go 1.17 +go 1.18 require ( github.com/stretchr/testify v1.7.1 diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/mock_collector_test.go b/exporters/otlp/otlpmetric/otlpmetrichttp/mock_collector_test.go index 5776c67a016..71e25ae2ffc 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/mock_collector_test.go +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/mock_collector_test.go @@ -12,6 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +//go:build go1.18 +// +build go1.18 + package otlpmetrichttp_test import ( diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/options.go b/exporters/otlp/otlpmetric/otlpmetrichttp/options.go index 8d12c791954..232483631bd 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/options.go +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/options.go @@ -12,6 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +//go:build go1.18 +// +build go1.18 + package otlpmetrichttp // import "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp" import ( From 07b3a70738390d4a53a1f5d4d9472327d32d16e8 Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Tue, 16 Aug 2022 12:29:26 -0700 Subject: [PATCH 09/34] Remove integration testing --- .../otlpmetrichttp/certificate_test.go | 95 ------ .../otlpmetric/otlpmetrichttp/client_test.go | 274 ------------------ .../otlpmetrichttp/mock_collector_test.go | 242 ---------------- 3 files changed, 611 deletions(-) delete mode 100644 exporters/otlp/otlpmetric/otlpmetrichttp/certificate_test.go delete mode 100644 exporters/otlp/otlpmetric/otlpmetrichttp/client_test.go delete mode 100644 exporters/otlp/otlpmetric/otlpmetrichttp/mock_collector_test.go diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/certificate_test.go b/exporters/otlp/otlpmetric/otlpmetrichttp/certificate_test.go deleted file mode 100644 index eb33e6e54a0..00000000000 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/certificate_test.go +++ /dev/null @@ -1,95 +0,0 @@ -// 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. - -//go:build go1.18 -// +build go1.18 - -package otlpmetrichttp_test - -import ( - "bytes" - "crypto/ecdsa" - "crypto/elliptic" - cryptorand "crypto/rand" - "crypto/x509" - "crypto/x509/pkix" - "encoding/pem" - "math/big" - mathrand "math/rand" - "net" - "time" -) - -type mathRandReader struct{} - -func (mathRandReader) Read(p []byte) (n int, err error) { - return mathrand.Read(p) -} - -var randReader mathRandReader - -type pemCertificate struct { - Certificate []byte - PrivateKey []byte -} - -// Based on https://golang.org/src/crypto/tls/generate_cert.go, -// simplified and weakened. -func generateWeakCertificate() (*pemCertificate, error) { - priv, err := ecdsa.GenerateKey(elliptic.P256(), randReader) - if err != nil { - return nil, err - } - keyUsage := x509.KeyUsageDigitalSignature - notBefore := time.Now() - notAfter := notBefore.Add(time.Hour) - serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) - serialNumber, err := cryptorand.Int(randReader, serialNumberLimit) - if err != nil { - return nil, err - } - template := x509.Certificate{ - SerialNumber: serialNumber, - Subject: pkix.Name{ - Organization: []string{"otel-go"}, - }, - NotBefore: notBefore, - NotAfter: notAfter, - KeyUsage: keyUsage, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, - BasicConstraintsValid: true, - DNSNames: []string{"localhost"}, - IPAddresses: []net.IP{net.IPv6loopback, net.IPv4(127, 0, 0, 1)}, - } - derBytes, err := x509.CreateCertificate(randReader, &template, &template, &priv.PublicKey, priv) - if err != nil { - return nil, err - } - certificateBuffer := new(bytes.Buffer) - if err := pem.Encode(certificateBuffer, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil { - return nil, err - } - privDERBytes, err := x509.MarshalPKCS8PrivateKey(priv) - if err != nil { - return nil, err - } - privBuffer := new(bytes.Buffer) - if err := pem.Encode(privBuffer, &pem.Block{Type: "PRIVATE KEY", Bytes: privDERBytes}); err != nil { - return nil, err - } - return &pemCertificate{ - Certificate: certificateBuffer.Bytes(), - PrivateKey: privBuffer.Bytes(), - }, nil -} diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/client_test.go b/exporters/otlp/otlpmetric/otlpmetrichttp/client_test.go deleted file mode 100644 index 4c8bc1bfcc4..00000000000 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/client_test.go +++ /dev/null @@ -1,274 +0,0 @@ -// 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. - -//go:build go1.18 -// +build go1.18 - -package otlpmetrichttp_test - -import ( - "context" - "net/http" - "os" - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "go.opentelemetry.io/otel/exporters/otlp/otlpmetric" - "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/internal/otlpmetrictest" - "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp" - "go.opentelemetry.io/otel/sdk/resource" -) - -const ( - relOtherMetricsPath = "post/metrics/here" - otherMetricsPath = "/post/metrics/here" -) - -var ( - oneRecord = otlpmetrictest.OneRecordReader() - - testResource = resource.Empty() -) - -var ( - testHeaders = map[string]string{ - "Otel-Go-Key-1": "somevalue", - "Otel-Go-Key-2": "someothervalue", - } -) - -func TestEndToEnd(t *testing.T) { - tests := []struct { - name string - opts []otlpmetrichttp.Option - mcCfg mockCollectorConfig - tls bool - }{ - { - name: "no extra options", - opts: nil, - }, - { - name: "with gzip compression", - opts: []otlpmetrichttp.Option{ - otlpmetrichttp.WithCompression(otlpmetrichttp.GzipCompression), - }, - }, - { - name: "with empty paths (forced to defaults)", - opts: []otlpmetrichttp.Option{ - otlpmetrichttp.WithURLPath(""), - }, - }, - { - name: "with relative paths", - opts: []otlpmetrichttp.Option{ - otlpmetrichttp.WithURLPath(relOtherMetricsPath), - }, - mcCfg: mockCollectorConfig{ - MetricsURLPath: otherMetricsPath, - }, - }, - { - name: "with TLS", - opts: nil, - mcCfg: mockCollectorConfig{ - WithTLS: true, - }, - tls: true, - }, - { - name: "with extra headers", - opts: []otlpmetrichttp.Option{ - otlpmetrichttp.WithHeaders(testHeaders), - }, - mcCfg: mockCollectorConfig{ - ExpectedHeaders: testHeaders, - }, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - mc := runMockCollector(t, tc.mcCfg) - defer mc.MustStop(t) - allOpts := []otlpmetrichttp.Option{ - otlpmetrichttp.WithEndpoint(mc.Endpoint()), - } - if tc.tls { - tlsConfig := mc.ClientTLSConfig() - require.NotNil(t, tlsConfig) - allOpts = append(allOpts, otlpmetrichttp.WithTLSClientConfig(tlsConfig)) - } else { - allOpts = append(allOpts, otlpmetrichttp.WithInsecure()) - } - allOpts = append(allOpts, tc.opts...) - client := otlpmetrichttp.NewClient(allOpts...) - ctx := context.Background() - exporter, err := otlpmetric.New(ctx, client) - if assert.NoError(t, err) { - defer func() { - assert.NoError(t, exporter.Shutdown(ctx)) - }() - otlpmetrictest.RunEndToEndTest(ctx, t, exporter, mc) - } - }) - } -} - -func TestExporterShutdown(t *testing.T) { - mc := runMockCollector(t, mockCollectorConfig{}) - defer func() { - _ = mc.Stop() - }() - - <-time.After(5 * time.Millisecond) - - otlpmetrictest.RunExporterShutdownTest(t, func() otlpmetric.Client { - return otlpmetrichttp.NewClient( - otlpmetrichttp.WithInsecure(), - otlpmetrichttp.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 := otlpmetrichttp.NewClient( - otlpmetrichttp.WithEndpoint(mc.Endpoint()), - otlpmetrichttp.WithInsecure(), - otlpmetrichttp.WithTimeout(time.Nanosecond), - ) - ctx := context.Background() - exporter, err := otlpmetric.New(ctx, client) - require.NoError(t, err) - defer func() { - assert.NoError(t, exporter.Shutdown(ctx)) - }() - err = exporter.Export(ctx, testResource, oneRecord) - assert.Equalf(t, true, os.IsTimeout(err), "expected timeout error, got: %v", err) -} - -func TestEmptyData(t *testing.T) { - mcCfg := mockCollectorConfig{} - mc := runMockCollector(t, mcCfg) - defer mc.MustStop(t) - driver := otlpmetrichttp.NewClient( - otlpmetrichttp.WithEndpoint(mc.Endpoint()), - otlpmetrichttp.WithInsecure(), - ) - ctx := context.Background() - exporter, err := otlpmetric.New(ctx, driver) - require.NoError(t, err) - defer func() { - assert.NoError(t, exporter.Shutdown(ctx)) - }() - assert.NoError(t, err) - err = exporter.Export(ctx, testResource, oneRecord) - assert.NoError(t, err) - assert.NotEmpty(t, mc.GetMetrics()) -} - -func TestCancelledContext(t *testing.T) { - statuses := []int{ - http.StatusBadRequest, - } - mcCfg := mockCollectorConfig{ - InjectHTTPStatus: statuses, - } - mc := runMockCollector(t, mcCfg) - defer mc.MustStop(t) - driver := otlpmetrichttp.NewClient( - otlpmetrichttp.WithEndpoint(mc.Endpoint()), - otlpmetrichttp.WithInsecure(), - ) - ctx, cancel := context.WithCancel(context.Background()) - exporter, err := otlpmetric.New(ctx, driver) - require.NoError(t, err) - defer func() { - assert.NoError(t, exporter.Shutdown(context.Background())) - }() - cancel() - _ = exporter.Export(ctx, testResource, oneRecord) - assert.Empty(t, mc.GetMetrics()) -} - -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 := otlpmetrichttp.NewClient( - otlpmetrichttp.WithEndpoint(mc.Endpoint()), - otlpmetrichttp.WithInsecure(), - otlpmetrichttp.WithBackoff(time.Minute), - ) - ctx := context.Background() - exporter, err := otlpmetric.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.Export(ctx, testResource, oneRecord) - assert.Error(t, err) - assert.Empty(t, mc.GetMetrics()) -} - -func TestStopWhileExporting(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 := otlpmetrichttp.NewClient( - otlpmetrichttp.WithEndpoint(mc.Endpoint()), - otlpmetrichttp.WithInsecure(), - otlpmetrichttp.WithBackoff(time.Minute), - ) - ctx := context.Background() - exporter, err := otlpmetric.New(ctx, driver) - require.NoError(t, err) - defer func() { - assert.NoError(t, exporter.Shutdown(ctx)) - }() - doneCh := make(chan struct{}) - go func() { - err := exporter.Export(ctx, testResource, oneRecord) - assert.Error(t, err) - assert.Empty(t, mc.GetMetrics()) - close(doneCh) - }() - <-time.After(time.Second) - err = exporter.Shutdown(ctx) - assert.NoError(t, err) - <-doneCh -} diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/mock_collector_test.go b/exporters/otlp/otlpmetric/otlpmetrichttp/mock_collector_test.go deleted file mode 100644 index 71e25ae2ffc..00000000000 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/mock_collector_test.go +++ /dev/null @@ -1,242 +0,0 @@ -// 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. - -//go:build go1.18 -// +build go1.18 - -package otlpmetrichttp_test - -import ( - "bytes" - "compress/gzip" - "context" - "crypto/tls" - "fmt" - "io" - "net" - "net/http" - "sync" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "google.golang.org/protobuf/proto" - - "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/internal/otlpconfig" - "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/internal/otlpmetrictest" - collectormetricpb "go.opentelemetry.io/proto/otlp/collector/metrics/v1" - metricpb "go.opentelemetry.io/proto/otlp/metrics/v1" -) - -type mockCollector struct { - endpoint string - server *http.Server - - spanLock sync.Mutex - metricsStorage otlpmetrictest.MetricsStorage - - injectHTTPStatus []int - injectContentType string - delay <-chan struct{} - - clientTLSConfig *tls.Config - expectedHeaders map[string]string -} - -func (c *mockCollector) Stop() error { - return c.server.Shutdown(context.Background()) -} - -func (c *mockCollector) MustStop(t *testing.T) { - assert.NoError(t, c.server.Shutdown(context.Background())) -} - -func (c *mockCollector) GetMetrics() []*metricpb.Metric { - c.spanLock.Lock() - defer c.spanLock.Unlock() - return c.metricsStorage.GetMetrics() -} - -func (c *mockCollector) Endpoint() string { - return c.endpoint -} - -func (c *mockCollector) ClientTLSConfig() *tls.Config { - return c.clientTLSConfig -} - -func (c *mockCollector) serveMetrics(w http.ResponseWriter, r *http.Request) { - if c.delay != nil { - select { - case <-c.delay: - case <-r.Context().Done(): - return - } - } - - if !c.checkHeaders(r) { - w.WriteHeader(http.StatusBadRequest) - return - } - response := collectormetricpb.ExportMetricsServiceResponse{} - rawResponse, err := proto.Marshal(&response) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - return - } - if injectedStatus := c.getInjectHTTPStatus(); injectedStatus != 0 { - writeReply(w, rawResponse, injectedStatus, c.injectContentType) - return - } - rawRequest, err := readRequest(r) - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - return - } - - request, err := unmarshalMetricsRequest(rawRequest, r.Header.Get("content-type")) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - return - } - writeReply(w, rawResponse, 0, c.injectContentType) - c.spanLock.Lock() - defer c.spanLock.Unlock() - c.metricsStorage.AddMetrics(request) -} - -func unmarshalMetricsRequest(rawRequest []byte, contentType string) (*collectormetricpb.ExportMetricsServiceRequest, error) { - request := &collectormetricpb.ExportMetricsServiceRequest{} - if contentType != "application/x-protobuf" { - return request, fmt.Errorf("invalid content-type: %s, only application/x-protobuf is supported", contentType) - } - err := proto.Unmarshal(rawRequest, request) - return request, err -} - -func (c *mockCollector) checkHeaders(r *http.Request) bool { - for k, v := range c.expectedHeaders { - got := r.Header.Get(k) - if got != v { - return false - } - } - return true -} - -func (c *mockCollector) getInjectHTTPStatus() int { - if len(c.injectHTTPStatus) == 0 { - return 0 - } - status := c.injectHTTPStatus[0] - c.injectHTTPStatus = c.injectHTTPStatus[1:] - if len(c.injectHTTPStatus) == 0 { - c.injectHTTPStatus = nil - } - return status -} - -func readRequest(r *http.Request) ([]byte, error) { - if r.Header.Get("Content-Encoding") == "gzip" { - return readGzipBody(r.Body) - } - return io.ReadAll(r.Body) -} - -func readGzipBody(body io.Reader) ([]byte, error) { - rawRequest := bytes.Buffer{} - gunzipper, err := gzip.NewReader(body) - if err != nil { - return nil, err - } - defer gunzipper.Close() - _, err = io.Copy(&rawRequest, gunzipper) - if err != nil { - return nil, err - } - return rawRequest.Bytes(), nil -} - -func writeReply(w http.ResponseWriter, rawResponse []byte, injectHTTPStatus int, injectContentType string) { - status := http.StatusOK - if injectHTTPStatus != 0 { - status = injectHTTPStatus - } - contentType := "application/x-protobuf" - if injectContentType != "" { - contentType = injectContentType - } - w.Header().Set("Content-Type", contentType) - w.WriteHeader(status) - _, _ = w.Write(rawResponse) -} - -type mockCollectorConfig struct { - MetricsURLPath string - Port int - InjectHTTPStatus []int - InjectContentType string - Delay <-chan struct{} - WithTLS bool - ExpectedHeaders map[string]string -} - -func (c *mockCollectorConfig) fillInDefaults() { - if c.MetricsURLPath == "" { - c.MetricsURLPath = otlpconfig.DefaultMetricsPath - } -} - -func runMockCollector(t *testing.T, cfg mockCollectorConfig) *mockCollector { - cfg.fillInDefaults() - ln, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", cfg.Port)) - require.NoError(t, err) - _, portStr, err := net.SplitHostPort(ln.Addr().String()) - require.NoError(t, err) - m := &mockCollector{ - endpoint: fmt.Sprintf("localhost:%s", portStr), - metricsStorage: otlpmetrictest.NewMetricsStorage(), - injectHTTPStatus: cfg.InjectHTTPStatus, - injectContentType: cfg.InjectContentType, - delay: cfg.Delay, - expectedHeaders: cfg.ExpectedHeaders, - } - mux := http.NewServeMux() - mux.Handle(cfg.MetricsURLPath, http.HandlerFunc(m.serveMetrics)) - server := &http.Server{ - Handler: mux, - } - if cfg.WithTLS { - pem, err := generateWeakCertificate() - require.NoError(t, err) - tlsCertificate, err := tls.X509KeyPair(pem.Certificate, pem.PrivateKey) - require.NoError(t, err) - server.TLSConfig = &tls.Config{ - Certificates: []tls.Certificate{tlsCertificate}, - } - - m.clientTLSConfig = &tls.Config{ - InsecureSkipVerify: true, - } - } - go func() { - if cfg.WithTLS { - _ = server.ServeTLS(ln, "", "") - } else { - _ = server.Serve(ln) - } - }() - m.server = server - return m -} From 3b8458d4ed0b7001731d7b4f8d13c5f90101b35c Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Tue, 16 Aug 2022 12:31:32 -0700 Subject: [PATCH 10/34] Rename client_unit_test.go to client_test.go --- .../otlpmetrichttp/{client_unit_test.go => client_test.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename exporters/otlp/otlpmetric/otlpmetrichttp/{client_unit_test.go => client_test.go} (100%) diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/client_unit_test.go b/exporters/otlp/otlpmetric/otlpmetrichttp/client_test.go similarity index 100% rename from exporters/otlp/otlpmetric/otlpmetrichttp/client_unit_test.go rename to exporters/otlp/otlpmetric/otlpmetrichttp/client_test.go From fee6cd7d1b21e3a51504cd4f0f2438aa246aedd7 Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Tue, 16 Aug 2022 12:31:55 -0700 Subject: [PATCH 11/34] Rename options.go to config.go --- .../otlp/otlpmetric/otlpmetrichttp/{options.go => config.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename exporters/otlp/otlpmetric/otlpmetrichttp/{options.go => config.go} (100%) diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/options.go b/exporters/otlp/otlpmetric/otlpmetrichttp/config.go similarity index 100% rename from exporters/otlp/otlpmetric/otlpmetrichttp/options.go rename to exporters/otlp/otlpmetric/otlpmetrichttp/config.go From 28cc1435f89f242ffcfba1a9bb82678d81621ec1 Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Tue, 16 Aug 2022 12:32:33 -0700 Subject: [PATCH 12/34] Remove the NewUnstarted func --- exporters/otlp/otlpmetric/otlpmetrichttp/exporter.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/exporter.go b/exporters/otlp/otlpmetric/otlpmetrichttp/exporter.go index faa4ba79198..230766bae71 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/exporter.go +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/exporter.go @@ -27,8 +27,3 @@ import ( func New(ctx context.Context, opts ...Option) (*otlpmetric.Exporter, error) { return otlpmetric.New(ctx, NewClient(opts...)) } - -// NewUnstarted constructs a new Exporter and does not start it. -func NewUnstarted(opts ...Option) *otlpmetric.Exporter { - return otlpmetric.NewUnstarted(NewClient(opts...)) -} From 35c2e24e8da32e9ec68427f93bdddceea1c78d84 Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Tue, 16 Aug 2022 12:33:45 -0700 Subject: [PATCH 13/34] Remove Start method from client --- exporters/otlp/otlpmetric/otlpmetrichttp/client.go | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/client.go b/exporters/otlp/otlpmetric/otlpmetrichttp/client.go index 3f12413be9d..4f2b7297af8 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/client.go +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/client.go @@ -100,17 +100,6 @@ func NewClient(opts ...Option) otlpmetric.Client { } } -// Start does nothing in a HTTP client. -func (d *client) Start(ctx context.Context) error { - // nothing to do - select { - case <-ctx.Done(): - return ctx.Err() - default: - } - return nil -} - // Stop shuts down the client and interrupt any in-flight request. func (d *client) Stop(ctx context.Context) error { d.stopOnce.Do(func() { From 18f2ead88be2cf04ae6bc36336ab164697ebd316 Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Tue, 16 Aug 2022 12:34:48 -0700 Subject: [PATCH 14/34] Add no-op ForceFlush method to client --- exporters/otlp/otlpmetric/otlpmetrichttp/client.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/client.go b/exporters/otlp/otlpmetric/otlpmetrichttp/client.go index 4f2b7297af8..f20dbdf2d75 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/client.go +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/client.go @@ -100,6 +100,9 @@ func NewClient(opts ...Option) otlpmetric.Client { } } +// ForceFlush does nothing, the client holds no state. +func (c *client) ForceFlush(ctx context.Context) error { return ctx.Err() } + // Stop shuts down the client and interrupt any in-flight request. func (d *client) Stop(ctx context.Context) error { d.stopOnce.Do(func() { From cdd614ecca00fac1e65321de60418cefc6b88370 Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Tue, 16 Aug 2022 12:35:47 -0700 Subject: [PATCH 15/34] Update otlpconfig pkg name to oconf --- .../otlp/otlpmetric/otlpmetrichttp/client.go | 8 ++-- .../otlp/otlpmetric/otlpmetrichttp/config.go | 40 +++++++++---------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/client.go b/exporters/otlp/otlpmetric/otlpmetrichttp/client.go index f20dbdf2d75..2f7a22e99f7 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/client.go +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/client.go @@ -34,7 +34,7 @@ import ( "go.opentelemetry.io/otel/exporters/otlp/internal/retry" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric" - "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/internal/otlpconfig" + "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/internal/oconf" colmetricpb "go.opentelemetry.io/proto/otlp/collector/metrics/v1" metricpb "go.opentelemetry.io/proto/otlp/metrics/v1" ) @@ -67,8 +67,8 @@ var ourTransport = &http.Transport{ type client struct { name string - cfg otlpconfig.SignalConfig - generalCfg otlpconfig.Config + cfg oconf.SignalConfig + generalCfg oconf.Config requestFunc retry.RequestFunc client *http.Client stopCh chan struct{} @@ -77,7 +77,7 @@ type client struct { // NewClient creates a new HTTP metric client. func NewClient(opts ...Option) otlpmetric.Client { - cfg := otlpconfig.NewHTTPConfig(asHTTPOptions(opts)...) + cfg := oconf.NewHTTPConfig(asHTTPOptions(opts)...) httpClient := &http.Client{ Transport: ourTransport, diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/config.go b/exporters/otlp/otlpmetric/otlpmetrichttp/config.go index 232483631bd..eba0ed767db 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/config.go +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/config.go @@ -22,31 +22,31 @@ import ( "time" "go.opentelemetry.io/otel/exporters/otlp/internal/retry" - "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/internal/otlpconfig" + "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/internal/oconf" ) // Compression describes the compression used for payloads sent to the // collector. -type Compression otlpconfig.Compression +type Compression oconf.Compression const ( // NoCompression tells the driver to send payloads without // compression. - NoCompression = Compression(otlpconfig.NoCompression) + NoCompression = Compression(oconf.NoCompression) // GzipCompression tells the driver to send payloads after // compressing them with gzip. - GzipCompression = Compression(otlpconfig.GzipCompression) + GzipCompression = Compression(oconf.GzipCompression) ) // Option applies an option to the HTTP client. type Option interface { - applyHTTPOption(otlpconfig.Config) otlpconfig.Config + applyHTTPOption(oconf.Config) oconf.Config } -func asHTTPOptions(opts []Option) []otlpconfig.HTTPOption { - converted := make([]otlpconfig.HTTPOption, len(opts)) +func asHTTPOptions(opts []Option) []oconf.HTTPOption { + converted := make([]oconf.HTTPOption, len(opts)) for i, o := range opts { - converted[i] = otlpconfig.NewHTTPOption(o.applyHTTPOption) + converted[i] = oconf.NewHTTPOption(o.applyHTTPOption) } return converted } @@ -56,10 +56,10 @@ func asHTTPOptions(opts []Option) []otlpconfig.HTTPOption { type RetryConfig retry.Config type wrappedOption struct { - otlpconfig.HTTPOption + oconf.HTTPOption } -func (w wrappedOption) applyHTTPOption(cfg otlpconfig.Config) otlpconfig.Config { +func (w wrappedOption) applyHTTPOption(cfg oconf.Config) oconf.Config { return w.ApplyHTTPOption(cfg) } @@ -68,18 +68,18 @@ func (w wrappedOption) applyHTTPOption(cfg otlpconfig.Config) otlpconfig.Config // the default endpoint (localhost:4318). Note that the endpoint must not // contain any URL path. func WithEndpoint(endpoint string) Option { - return wrappedOption{otlpconfig.WithEndpoint(endpoint)} + return wrappedOption{oconf.WithEndpoint(endpoint)} } // WithCompression tells the driver to compress the sent data. func WithCompression(compression Compression) Option { - return wrappedOption{otlpconfig.WithCompression(otlpconfig.Compression(compression))} + return wrappedOption{oconf.WithCompression(oconf.Compression(compression))} } // WithURLPath allows one to override the default URL path used // for sending metrics. If unset, default ("/v1/metrics") will be used. func WithURLPath(urlPath string) Option { - return wrappedOption{otlpconfig.WithURLPath(urlPath)} + return wrappedOption{oconf.WithURLPath(urlPath)} } // WithMaxAttempts allows one to override how many times the driver @@ -93,7 +93,7 @@ func WithMaxAttempts(maxAttempts int) Option { maxAttempts = 5 } return wrappedOption{ - otlpconfig.NewHTTPOption(func(cfg otlpconfig.Config) otlpconfig.Config { + oconf.NewHTTPOption(func(cfg oconf.Config) oconf.Config { cfg.RetryConfig.Enabled = true var ( @@ -138,7 +138,7 @@ func WithBackoff(duration time.Duration) Option { duration = 300 * time.Millisecond } return wrappedOption{ - otlpconfig.NewHTTPOption(func(cfg otlpconfig.Config) otlpconfig.Config { + oconf.NewHTTPOption(func(cfg oconf.Config) oconf.Config { cfg.RetryConfig.Enabled = true cfg.RetryConfig.MaxInterval = duration if cfg.RetryConfig.InitialInterval == 0 { @@ -156,26 +156,26 @@ func WithBackoff(duration time.Duration) Option { // configuration for the client used to send payloads to the // collector. Use it if you want to use a custom certificate. func WithTLSClientConfig(tlsCfg *tls.Config) Option { - return wrappedOption{otlpconfig.WithTLSClientConfig(tlsCfg)} + return wrappedOption{oconf.WithTLSClientConfig(tlsCfg)} } // WithInsecure tells the driver to connect to the collector using the // HTTP scheme, instead of HTTPS. func WithInsecure() Option { - return wrappedOption{otlpconfig.WithInsecure()} + return wrappedOption{oconf.WithInsecure()} } // WithHeaders allows one to tell the driver to send additional HTTP // headers with the payloads. Specifying headers like Content-Length, // Content-Encoding and Content-Type may result in a broken driver. func WithHeaders(headers map[string]string) Option { - return wrappedOption{otlpconfig.WithHeaders(headers)} + return wrappedOption{oconf.WithHeaders(headers)} } // WithTimeout tells the driver the max waiting time for the backend to process // each metrics batch. If unset, the default will be 10 seconds. func WithTimeout(duration time.Duration) Option { - return wrappedOption{otlpconfig.WithTimeout(duration)} + return wrappedOption{oconf.WithTimeout(duration)} } // WithRetry configures the retry policy for transient errors that may occurs @@ -184,5 +184,5 @@ func WithTimeout(duration time.Duration) Option { // policy will retry after 5 seconds and increase exponentially after each // error for a total of 1 minute. func WithRetry(rc RetryConfig) Option { - return wrappedOption{otlpconfig.WithRetry(retry.Config(rc))} + return wrappedOption{oconf.WithRetry(retry.Config(rc))} } From 516346bffd0c9d498d624ed05ab5b9cb45e96851 Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Tue, 16 Aug 2022 12:36:49 -0700 Subject: [PATCH 16/34] Rename Stop method to Shutdown Match the otlpmetric.Client interface. --- exporters/otlp/otlpmetric/otlpmetrichttp/client.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/client.go b/exporters/otlp/otlpmetric/otlpmetrichttp/client.go index 2f7a22e99f7..dd1ea4a9d55 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/client.go +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/client.go @@ -103,8 +103,8 @@ func NewClient(opts ...Option) otlpmetric.Client { // ForceFlush does nothing, the client holds no state. func (c *client) ForceFlush(ctx context.Context) error { return ctx.Err() } -// Stop shuts down the client and interrupt any in-flight request. -func (d *client) Stop(ctx context.Context) error { +// Shutdown shuts down the client and interrupt any in-flight request. +func (d *client) Shutdown(ctx context.Context) error { d.stopOnce.Do(func() { close(d.stopCh) }) From 6fdde7ea89718d8c34f1b9ec79c558c079a67092 Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Tue, 16 Aug 2022 12:40:21 -0700 Subject: [PATCH 17/34] Update creation functions to compile --- exporters/otlp/otlpmetric/otlpmetrichttp/client.go | 4 ++-- exporters/otlp/otlpmetric/otlpmetrichttp/exporter.go | 9 +++++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/client.go b/exporters/otlp/otlpmetric/otlpmetrichttp/client.go index dd1ea4a9d55..efaad77bfb4 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/client.go +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/client.go @@ -76,7 +76,7 @@ type client struct { } // NewClient creates a new HTTP metric client. -func NewClient(opts ...Option) otlpmetric.Client { +func NewClient(ctx context.Context, opts ...Option) (otlpmetric.Client, error) { cfg := oconf.NewHTTPConfig(asHTTPOptions(opts)...) httpClient := &http.Client{ @@ -97,7 +97,7 @@ func NewClient(opts ...Option) otlpmetric.Client { requestFunc: cfg.RetryConfig.RequestFunc(evaluate), stopCh: stopCh, client: httpClient, - } + }, nil } // ForceFlush does nothing, the client holds no state. diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/exporter.go b/exporters/otlp/otlpmetric/otlpmetrichttp/exporter.go index 230766bae71..0f83c73c405 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/exporter.go +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/exporter.go @@ -21,9 +21,14 @@ import ( "context" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric" + "go.opentelemetry.io/otel/sdk/metric" ) // New constructs a new Exporter and starts it. -func New(ctx context.Context, opts ...Option) (*otlpmetric.Exporter, error) { - return otlpmetric.New(ctx, NewClient(opts...)) +func New(ctx context.Context, opts ...Option) (metric.Exporter, error) { + c, err := NewClient(ctx, opts...) + if err != nil { + return nil, err + } + return otlpmetric.New(c), nil } From a8898599064d1523382f0d8ecaac223a09cde3ae Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Tue, 16 Aug 2022 13:08:21 -0700 Subject: [PATCH 18/34] Remove name field from client --- exporters/otlp/otlpmetric/otlpmetrichttp/client.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/client.go b/exporters/otlp/otlpmetric/otlpmetrichttp/client.go index efaad77bfb4..85205c0428a 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/client.go +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/client.go @@ -66,7 +66,6 @@ var ourTransport = &http.Transport{ } type client struct { - name string cfg oconf.SignalConfig generalCfg oconf.Config requestFunc retry.RequestFunc @@ -91,7 +90,6 @@ func NewClient(ctx context.Context, opts ...Option) (otlpmetric.Client, error) { stopCh := make(chan struct{}) return &client{ - name: "metrics", cfg: cfg.Metrics, generalCfg: cfg, requestFunc: cfg.RetryConfig.RequestFunc(evaluate), @@ -162,7 +160,7 @@ func (d *client) UploadMetrics(ctx context.Context, protoMetrics *metricpb.Resou return err } default: - rErr = fmt.Errorf("failed to send %s to %s: %s", d.name, request.URL, resp.Status) + rErr = fmt.Errorf("failed to send metrics to %s: %s", request.URL, resp.Status) } if err := resp.Body.Close(); err != nil { From 6295a37abd4f75b8412fb47cb26318af040bf6b2 Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Tue, 16 Aug 2022 13:16:32 -0700 Subject: [PATCH 19/34] Remove sync of methods from client This is handled by the exporter. --- .../otlp/otlpmetric/otlpmetrichttp/client.go | 60 +++++++------------ 1 file changed, 21 insertions(+), 39 deletions(-) diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/client.go b/exporters/otlp/otlpmetric/otlpmetrichttp/client.go index 85205c0428a..7a943e4caa8 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/client.go +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/client.go @@ -70,8 +70,6 @@ type client struct { generalCfg oconf.Config requestFunc retry.RequestFunc client *http.Client - stopCh chan struct{} - stopOnce sync.Once } // NewClient creates a new HTTP metric client. @@ -88,12 +86,10 @@ func NewClient(ctx context.Context, opts ...Option) (otlpmetric.Client, error) { httpClient.Transport = transport } - stopCh := make(chan struct{}) return &client{ cfg: cfg.Metrics, generalCfg: cfg, requestFunc: cfg.RetryConfig.RequestFunc(evaluate), - stopCh: stopCh, client: httpClient, }, nil } @@ -101,21 +97,26 @@ func NewClient(ctx context.Context, opts ...Option) (otlpmetric.Client, error) { // ForceFlush does nothing, the client holds no state. func (c *client) ForceFlush(ctx context.Context) error { return ctx.Err() } -// Shutdown shuts down the client and interrupt any in-flight request. -func (d *client) Shutdown(ctx context.Context) error { - d.stopOnce.Do(func() { - close(d.stopCh) - }) - select { - case <-ctx.Done(): - return ctx.Err() - default: - } - return nil +// Shutdown shuts down the client, freeing all resources. +func (c *client) Shutdown(ctx context.Context) error { + // The otlpmetric.Exporter synchronizes access to client methods and + // ensures this is called only once. The only thing that needs to be done + // here is to release any computational resources the client holds. + + c.requestFunc = nil + c.client = nil + return ctx.Err() } -// UploadMetrics sends a batch of metrics to the collector. -func (d *client) UploadMetrics(ctx context.Context, protoMetrics *metricpb.ResourceMetrics) error { +// UploadMetrics sends protoMetrics to connected endpoint. +// +// Retryable errors from the server will be handled according to any +// RetryConfig the client was created with. +func (c *client) UploadMetrics(ctx context.Context, protoMetrics *metricpb.ResourceMetrics) error { + // The otlpmetric.Exporter synchronizes access to client methods, and + // ensures this is not called after the Exporter is shutdown. Only thing + // to do here is send data. + pbRequest := &colmetricpb.ExportMetricsServiceRequest{ ResourceMetrics: []*metricpb.ResourceMetrics{protoMetrics}, } @@ -124,15 +125,12 @@ func (d *client) UploadMetrics(ctx context.Context, protoMetrics *metricpb.Resou return err } - ctx, cancel := d.contextWithStop(ctx) - defer cancel() - - request, err := d.newRequest(rawRequest) + request, err := c.newRequest(rawRequest) if err != nil { return err } - return d.requestFunc(ctx, func(ctx context.Context) error { + return c.requestFunc(ctx, func(ctx context.Context) error { select { case <-ctx.Done(): return ctx.Err() @@ -140,7 +138,7 @@ func (d *client) UploadMetrics(ctx context.Context, protoMetrics *metricpb.Resou } request.reset(ctx) - resp, err := d.client.Do(request.Request) + resp, err := c.client.Do(request.Request) if err != nil { return err } @@ -275,19 +273,3 @@ func (d *client) getScheme() string { } return "https" } - -func (d *client) contextWithStop(ctx context.Context) (context.Context, context.CancelFunc) { - // Unify the parent context Done signal with the client's stop - // channel. - ctx, cancel := context.WithCancel(ctx) - go func(ctx context.Context, cancel context.CancelFunc) { - select { - case <-ctx.Done(): - // Nothing to do, either cancelled or deadline - // happened. - case <-d.stopCh: - cancel() - } - }(ctx, cancel) - return ctx, cancel -} From 0cf6737433962a042d0a47682818f385438b80ad Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Tue, 16 Aug 2022 13:21:51 -0700 Subject: [PATCH 20/34] Remove unused generalCfg field from client --- exporters/otlp/otlpmetric/otlpmetrichttp/client.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/client.go b/exporters/otlp/otlpmetric/otlpmetrichttp/client.go index 7a943e4caa8..450fa28a15c 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/client.go +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/client.go @@ -51,7 +51,7 @@ var gzPool = sync.Pool{ // Keep it in sync with golang's DefaultTransport from net/http! We // have our own copy to avoid handling a situation where the // DefaultTransport is overwritten with some different implementation -// of http.RoundTripper or it's modified by other package. +// of http.RoundTripper or it's modified by another package. var ourTransport = &http.Transport{ Proxy: http.ProxyFromEnvironment, DialContext: (&net.Dialer{ @@ -67,7 +67,6 @@ var ourTransport = &http.Transport{ type client struct { cfg oconf.SignalConfig - generalCfg oconf.Config requestFunc retry.RequestFunc client *http.Client } @@ -88,7 +87,6 @@ func NewClient(ctx context.Context, opts ...Option) (otlpmetric.Client, error) { return &client{ cfg: cfg.Metrics, - generalCfg: cfg, requestFunc: cfg.RetryConfig.RequestFunc(evaluate), client: httpClient, }, nil @@ -108,7 +106,7 @@ func (c *client) Shutdown(ctx context.Context) error { return ctx.Err() } -// UploadMetrics sends protoMetrics to connected endpoint. +// UploadMetrics sends protoMetrics to the connected endpoint. // // Retryable errors from the server will be handled according to any // RetryConfig the client was created with. From 1b99035481a853ed1189d3b0426d9f2594cb589d Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Tue, 16 Aug 2022 13:48:02 -0700 Subject: [PATCH 21/34] Replace cfg client field with used conf vals --- .../otlp/otlpmetric/otlpmetrichttp/client.go | 44 +++++++++++++------ 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/client.go b/exporters/otlp/otlpmetric/otlpmetrichttp/client.go index 450fa28a15c..98ad48fe64e 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/client.go +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/client.go @@ -66,7 +66,9 @@ var ourTransport = &http.Transport{ } type client struct { - cfg oconf.SignalConfig + url string + compression Compression + headers map[string]string requestFunc retry.RequestFunc client *http.Client } @@ -85,8 +87,30 @@ func NewClient(ctx context.Context, opts ...Option) (otlpmetric.Client, error) { httpClient.Transport = transport } + format := "https://%s%s" + if cfg.Metrics.Insecure { + format = "http://%s%s" + } + rawURL := fmt.Sprintf(format, cfg.Metrics.Endpoint, cfg.Metrics.URLPath) + // Ensure target URL is valid at start, instead of every export call. + u, err := url.Parse(rawURL) + if err != nil { + return nil, err + } + + // Make a copy of headers so the underlying value does not change. + var h map[string]string + if n := len(cfg.Metrics.Headers); n > 0 { + h = make(map[string]string, n) + for k, v := range cfg.Metrics.Headers { + h[k] = v + } + } + return &client{ - cfg: cfg.Metrics, + url: u.String(), + compression: Compression(cfg.Metrics.Compression), + headers: h, requestFunc: cfg.RetryConfig.RequestFunc(evaluate), client: httpClient, }, nil @@ -166,20 +190,19 @@ func (c *client) UploadMetrics(ctx context.Context, protoMetrics *metricpb.Resou }) } -func (d *client) newRequest(body []byte) (request, error) { - u := url.URL{Scheme: d.getScheme(), Host: d.cfg.Endpoint, Path: d.cfg.URLPath} - r, err := http.NewRequest(http.MethodPost, u.String(), nil) +func (c *client) newRequest(body []byte) (request, error) { + r, err := http.NewRequest(http.MethodPost, c.url, nil) if err != nil { return request{Request: r}, err } - for k, v := range d.cfg.Headers { + for k, v := range c.headers { r.Header.Set(k, v) } r.Header.Set("Content-Type", contentTypeProto) req := request{Request: r} - switch Compression(d.cfg.Compression) { + switch c.compression { case NoCompression: r.ContentLength = (int64)(len(body)) req.bodyReader = bodyReader(body) @@ -264,10 +287,3 @@ func evaluate(err error) (bool, time.Duration) { return true, time.Duration(rErr.throttle) } - -func (d *client) getScheme() string { - if d.cfg.Insecure { - return "http" - } - return "https" -} From 39ed504ce2fc6c0b387c02d689abcfc46bc70493 Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Wed, 17 Aug 2022 14:40:07 -0700 Subject: [PATCH 22/34] Use a http request instead of url/header fields --- .../otlp/otlpmetric/otlpmetrichttp/client.go | 45 +++++++------------ 1 file changed, 16 insertions(+), 29 deletions(-) diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/client.go b/exporters/otlp/otlpmetric/otlpmetrichttp/client.go index 98ad48fe64e..a4eb13cd9f3 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/client.go +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/client.go @@ -25,7 +25,6 @@ import ( "io" "net" "net/http" - "net/url" "strconv" "sync" "time" @@ -66,9 +65,9 @@ var ourTransport = &http.Transport{ } type client struct { - url string + // req is cloned for every upload the client makes. + req *http.Request compression Compression - headers map[string]string requestFunc retry.RequestFunc client *http.Client } @@ -92,25 +91,22 @@ func NewClient(ctx context.Context, opts ...Option) (otlpmetric.Client, error) { format = "http://%s%s" } rawURL := fmt.Sprintf(format, cfg.Metrics.Endpoint, cfg.Metrics.URLPath) - // Ensure target URL is valid at start, instead of every export call. - u, err := url.Parse(rawURL) + // Body is set when this is cloned during upload. + req, err := http.NewRequest(http.MethodPost, rawURL, http.NoBody) if err != nil { return nil, err } - // Make a copy of headers so the underlying value does not change. - var h map[string]string if n := len(cfg.Metrics.Headers); n > 0 { - h = make(map[string]string, n) for k, v := range cfg.Metrics.Headers { - h[k] = v + req.Header.Set(k, v) } } + req.Header.Set("Content-Type", contentTypeProto) return &client{ - url: u.String(), compression: Compression(cfg.Metrics.Compression), - headers: h, + req: req, requestFunc: cfg.RetryConfig.RequestFunc(evaluate), client: httpClient, }, nil @@ -142,24 +138,23 @@ func (c *client) UploadMetrics(ctx context.Context, protoMetrics *metricpb.Resou pbRequest := &colmetricpb.ExportMetricsServiceRequest{ ResourceMetrics: []*metricpb.ResourceMetrics{protoMetrics}, } - rawRequest, err := proto.Marshal(pbRequest) + body, err := proto.Marshal(pbRequest) if err != nil { return err } - - request, err := c.newRequest(rawRequest) + request, err := c.newRequest(ctx, body) if err != nil { return err } - return c.requestFunc(ctx, func(ctx context.Context) error { + return c.requestFunc(ctx, func(iCtx context.Context) error { select { - case <-ctx.Done(): - return ctx.Err() + case <-iCtx.Done(): + return iCtx.Err() default: } - request.reset(ctx) + request.reset(iCtx) resp, err := c.client.Do(request.Request) if err != nil { return err @@ -190,18 +185,10 @@ func (c *client) UploadMetrics(ctx context.Context, protoMetrics *metricpb.Resou }) } -func (c *client) newRequest(body []byte) (request, error) { - r, err := http.NewRequest(http.MethodPost, c.url, nil) - if err != nil { - return request{Request: r}, err - } - - for k, v := range c.headers { - r.Header.Set(k, v) - } - r.Header.Set("Content-Type", contentTypeProto) - +func (c *client) newRequest(ctx context.Context, body []byte) (request, error) { + r := c.req.Clone(ctx) req := request{Request: r} + switch c.compression { case NoCompression: r.ContentLength = (int64)(len(body)) From f8579498424cdb17c1253d8691ee180ba1070433 Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Wed, 17 Aug 2022 14:43:56 -0700 Subject: [PATCH 23/34] Remove NewClient and move New into client.go --- .../otlp/otlpmetric/otlpmetrichttp/client.go | 44 ++++++++++++------- .../otlpmetric/otlpmetrichttp/client_test.go | 4 +- .../otlpmetric/otlpmetrichttp/exporter.go | 34 -------------- 3 files changed, 29 insertions(+), 53 deletions(-) delete mode 100644 exporters/otlp/otlpmetric/otlpmetrichttp/exporter.go diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/client.go b/exporters/otlp/otlpmetric/otlpmetrichttp/client.go index a4eb13cd9f3..94bbf25538a 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/client.go +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/client.go @@ -34,17 +34,28 @@ import ( "go.opentelemetry.io/otel/exporters/otlp/internal/retry" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/internal/oconf" + "go.opentelemetry.io/otel/sdk/metric" colmetricpb "go.opentelemetry.io/proto/otlp/collector/metrics/v1" metricpb "go.opentelemetry.io/proto/otlp/metrics/v1" ) -const contentTypeProto = "application/x-protobuf" +// New returns an OpenTelemetry metric Exporter. The Exporter can be used with +// a PeriodicReader to export OpenTelemetry metric data to an OTLP receiving +// endpoint using protobufs over HTTP. +func New(ctx context.Context, opts ...Option) (metric.Exporter, error) { + c, err := newClient(ctx, opts...) + if err != nil { + return nil, err + } + return otlpmetric.New(c), nil +} -var gzPool = sync.Pool{ - New: func() interface{} { - w := gzip.NewWriter(io.Discard) - return w - }, +type client struct { + // req is cloned for every upload the client makes. + req *http.Request + compression Compression + requestFunc retry.RequestFunc + client *http.Client } // Keep it in sync with golang's DefaultTransport from net/http! We @@ -64,16 +75,8 @@ var ourTransport = &http.Transport{ ExpectContinueTimeout: 1 * time.Second, } -type client struct { - // req is cloned for every upload the client makes. - req *http.Request - compression Compression - requestFunc retry.RequestFunc - client *http.Client -} - -// NewClient creates a new HTTP metric client. -func NewClient(ctx context.Context, opts ...Option) (otlpmetric.Client, error) { +// newClient creates a new HTTP metric client. +func newClient(ctx context.Context, opts ...Option) (otlpmetric.Client, error) { cfg := oconf.NewHTTPConfig(asHTTPOptions(opts)...) httpClient := &http.Client{ @@ -102,7 +105,7 @@ func NewClient(ctx context.Context, opts ...Option) (otlpmetric.Client, error) { req.Header.Set(k, v) } } - req.Header.Set("Content-Type", contentTypeProto) + req.Header.Set("Content-Type", "application/x-protobuf") return &client{ compression: Compression(cfg.Metrics.Compression), @@ -185,6 +188,13 @@ func (c *client) UploadMetrics(ctx context.Context, protoMetrics *metricpb.Resou }) } +var gzPool = sync.Pool{ + New: func() interface{} { + w := gzip.NewWriter(io.Discard) + return w + }, +} + func (c *client) newRequest(ctx context.Context, body []byte) (request, error) { r := c.req.Clone(ctx) req := request{Request: r} diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/client_test.go b/exporters/otlp/otlpmetric/otlpmetrichttp/client_test.go index fd6dadecb89..4ccf85090b2 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/client_test.go +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/client_test.go @@ -26,7 +26,7 @@ import ( ) func TestUnreasonableBackoff(t *testing.T) { - cIface := NewClient( + cIface := newClient( WithEndpoint("http://localhost"), WithInsecure(), WithBackoff(-time.Microsecond), @@ -55,7 +55,7 @@ func TestUnreasonableMaxAttempts(t *testing.T) { }, } { t.Run(tc.name, func(t *testing.T) { - cIface := NewClient( + cIface := newClient( WithEndpoint("http://localhost"), WithInsecure(), WithMaxAttempts(tc.maxAttempts), diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/exporter.go b/exporters/otlp/otlpmetric/otlpmetrichttp/exporter.go deleted file mode 100644 index 0f83c73c405..00000000000 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/exporter.go +++ /dev/null @@ -1,34 +0,0 @@ -// 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. - -//go:build go1.18 -// +build go1.18 - -package otlpmetrichttp // import "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp" - -import ( - "context" - - "go.opentelemetry.io/otel/exporters/otlp/otlpmetric" - "go.opentelemetry.io/otel/sdk/metric" -) - -// New constructs a new Exporter and starts it. -func New(ctx context.Context, opts ...Option) (metric.Exporter, error) { - c, err := NewClient(ctx, opts...) - if err != nil { - return nil, err - } - return otlpmetric.New(c), nil -} From 0e2ad2223c0a5423893e66f45b06e93f04f90f4c Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Wed, 17 Aug 2022 14:44:39 -0700 Subject: [PATCH 24/34] Rename client.client field to client.httpClient --- exporters/otlp/otlpmetric/otlpmetrichttp/client.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/client.go b/exporters/otlp/otlpmetric/otlpmetrichttp/client.go index 94bbf25538a..dc8760c160a 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/client.go +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/client.go @@ -55,7 +55,7 @@ type client struct { req *http.Request compression Compression requestFunc retry.RequestFunc - client *http.Client + httpClient *http.Client } // Keep it in sync with golang's DefaultTransport from net/http! We @@ -111,7 +111,7 @@ func newClient(ctx context.Context, opts ...Option) (otlpmetric.Client, error) { compression: Compression(cfg.Metrics.Compression), req: req, requestFunc: cfg.RetryConfig.RequestFunc(evaluate), - client: httpClient, + httpClient: httpClient, }, nil } @@ -125,7 +125,7 @@ func (c *client) Shutdown(ctx context.Context) error { // here is to release any computational resources the client holds. c.requestFunc = nil - c.client = nil + c.httpClient = nil return ctx.Err() } @@ -158,7 +158,7 @@ func (c *client) UploadMetrics(ctx context.Context, protoMetrics *metricpb.Resou } request.reset(iCtx) - resp, err := c.client.Do(request.Request) + resp, err := c.httpClient.Do(request.Request) if err != nil { return err } From 6bef8fa6d161f39904b1ca916a3a277a83e9d698 Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Wed, 17 Aug 2022 14:50:05 -0700 Subject: [PATCH 25/34] Update client tests Remove test of a retry config and add functional tests of the client methods honoring a context. --- .../otlp/otlpmetric/otlpmetrichttp/client.go | 6 +- .../otlpmetric/otlpmetrichttp/client_test.go | 81 ++++++++++--------- 2 files changed, 45 insertions(+), 42 deletions(-) diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/client.go b/exporters/otlp/otlpmetric/otlpmetrichttp/client.go index dc8760c160a..1686bc692a4 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/client.go +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/client.go @@ -42,8 +42,8 @@ import ( // New returns an OpenTelemetry metric Exporter. The Exporter can be used with // a PeriodicReader to export OpenTelemetry metric data to an OTLP receiving // endpoint using protobufs over HTTP. -func New(ctx context.Context, opts ...Option) (metric.Exporter, error) { - c, err := newClient(ctx, opts...) +func New(opts ...Option) (metric.Exporter, error) { + c, err := newClient(opts...) if err != nil { return nil, err } @@ -76,7 +76,7 @@ var ourTransport = &http.Transport{ } // newClient creates a new HTTP metric client. -func newClient(ctx context.Context, opts ...Option) (otlpmetric.Client, error) { +func newClient(opts ...Option) (otlpmetric.Client, error) { cfg := oconf.NewHTTPConfig(asHTTPOptions(opts)...) httpClient := &http.Client{ diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/client_test.go b/exporters/otlp/otlpmetric/otlpmetrichttp/client_test.go index 4ccf85090b2..e78a0f9fb5c 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/client_test.go +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/client_test.go @@ -18,6 +18,7 @@ package otlpmetrichttp import ( + "context" "testing" "time" @@ -25,47 +26,49 @@ import ( "github.com/stretchr/testify/require" ) -func TestUnreasonableBackoff(t *testing.T) { - cIface := newClient( - WithEndpoint("http://localhost"), - WithInsecure(), - WithBackoff(-time.Microsecond), - ) - require.IsType(t, &client{}, cIface) - c := cIface.(*client) - assert.True(t, c.generalCfg.RetryConfig.Enabled) - assert.Equal(t, 5*time.Second, c.generalCfg.RetryConfig.InitialInterval) - assert.Equal(t, 300*time.Millisecond, c.generalCfg.RetryConfig.MaxInterval) - assert.Equal(t, time.Minute, c.generalCfg.RetryConfig.MaxElapsedTime) +func TestClientHonorsContextErrors(t *testing.T) { + t.Run("Shutdown", testCtxErr(func(t *testing.T) func(context.Context) error { + c, err := newClient() + require.NoError(t, err) + return c.Shutdown + })) + + t.Run("ForceFlush", testCtxErr(func(t *testing.T) func(context.Context) error { + c, err := newClient() + require.NoError(t, err) + return c.ForceFlush + })) + + t.Run("UploadMetrics", testCtxErr(func(t *testing.T) func(context.Context) error { + c, err := newClient() + require.NoError(t, err) + return func(ctx context.Context) error { + return c.UploadMetrics(ctx, nil) + } + })) } -func TestUnreasonableMaxAttempts(t *testing.T) { - type testcase struct { - name string - maxAttempts int - } - for _, tc := range []testcase{ - { - name: "negative max attempts", - maxAttempts: -3, - }, - { - name: "too large max attempts", - maxAttempts: 10, - }, - } { - t.Run(tc.name, func(t *testing.T) { - cIface := newClient( - WithEndpoint("http://localhost"), - WithInsecure(), - WithMaxAttempts(tc.maxAttempts), - ) - require.IsType(t, &client{}, cIface) - c := cIface.(*client) - assert.True(t, c.generalCfg.RetryConfig.Enabled) - assert.Equal(t, 5*time.Second, c.generalCfg.RetryConfig.InitialInterval) - assert.Equal(t, 30*time.Second, c.generalCfg.RetryConfig.MaxInterval) - assert.Equal(t, 145*time.Second, c.generalCfg.RetryConfig.MaxElapsedTime) +func testCtxErr(factory func(*testing.T) func(context.Context) error) func(t *testing.T) { + return func(t *testing.T) { + t.Helper() + ctx, cancel := context.WithCancel(context.Background()) + t.Cleanup(cancel) + + t.Run("DeadlineExceeded", func(t *testing.T) { + innerCtx, innerCancel := context.WithTimeout(ctx, time.Nanosecond) + t.Cleanup(innerCancel) + <-innerCtx.Done() + + f := factory(t) + assert.ErrorIs(t, f(innerCtx), context.DeadlineExceeded) + }) + + t.Run("Canceled", func(t *testing.T) { + innerCtx, innerCancel := context.WithCancel(ctx) + innerCancel() + + f := factory(t) + assert.ErrorIs(t, f(innerCtx), context.Canceled) }) } } From 9b0200e510b98b3696dbe4748693696775076c65 Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Wed, 17 Aug 2022 14:57:13 -0700 Subject: [PATCH 26/34] Remove deprecated WithMaxAttempts and WithBackoff --- .../otlp/otlpmetric/otlpmetrichttp/config.go | 70 ------------------- 1 file changed, 70 deletions(-) diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/config.go b/exporters/otlp/otlpmetric/otlpmetrichttp/config.go index eba0ed767db..7a84a0795be 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/config.go +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/config.go @@ -82,76 +82,6 @@ func WithURLPath(urlPath string) Option { return wrappedOption{oconf.WithURLPath(urlPath)} } -// WithMaxAttempts allows one to override how many times the driver -// will try to send the payload in case of retryable errors. -// The max attempts is limited to at most 5 retries. If unset, -// default (5) will be used. -// -// Deprecated: Use WithRetry instead. -func WithMaxAttempts(maxAttempts int) Option { - if maxAttempts > 5 || maxAttempts < 0 { - maxAttempts = 5 - } - return wrappedOption{ - oconf.NewHTTPOption(func(cfg oconf.Config) oconf.Config { - cfg.RetryConfig.Enabled = true - - var ( - init = cfg.RetryConfig.InitialInterval - maxI = cfg.RetryConfig.MaxInterval - maxE = cfg.RetryConfig.MaxElapsedTime - ) - - if init == 0 { - init = retry.DefaultConfig.InitialInterval - } - if maxI == 0 { - maxI = retry.DefaultConfig.MaxInterval - } - if maxE == 0 { - maxE = retry.DefaultConfig.MaxElapsedTime - } - attempts := int64(maxE+init) / int64(maxI) - - if int64(maxAttempts) == attempts { - return cfg - } - - maxE = time.Duration(int64(maxAttempts)*int64(maxI)) - init - - cfg.RetryConfig.InitialInterval = init - cfg.RetryConfig.MaxInterval = maxI - cfg.RetryConfig.MaxElapsedTime = maxE - - return cfg - }), - } -} - -// WithBackoff tells the driver to use the duration as a base of the -// exponential backoff strategy. If unset, default (300ms) will be -// used. -// -// Deprecated: Use WithRetry instead. -func WithBackoff(duration time.Duration) Option { - if duration < 0 { - duration = 300 * time.Millisecond - } - return wrappedOption{ - oconf.NewHTTPOption(func(cfg oconf.Config) oconf.Config { - cfg.RetryConfig.Enabled = true - cfg.RetryConfig.MaxInterval = duration - if cfg.RetryConfig.InitialInterval == 0 { - cfg.RetryConfig.InitialInterval = retry.DefaultConfig.InitialInterval - } - if cfg.RetryConfig.MaxElapsedTime == 0 { - cfg.RetryConfig.MaxElapsedTime = retry.DefaultConfig.MaxElapsedTime - } - return cfg - }), - } -} - // WithTLSClientConfig can be used to set up a custom TLS // configuration for the client used to send payloads to the // collector. Use it if you want to use a custom certificate. From 060f304c202f233453027a16aec41cc519425018 Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Wed, 17 Aug 2022 15:16:16 -0700 Subject: [PATCH 27/34] Update option docs Include info on envvars. --- .../otlp/otlpmetric/otlpmetrichttp/config.go | 117 ++++++++++++++---- 1 file changed, 92 insertions(+), 25 deletions(-) diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/config.go b/exporters/otlp/otlpmetric/otlpmetrichttp/config.go index 7a84a0795be..77b8cbd6e5d 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/config.go +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/config.go @@ -38,7 +38,7 @@ const ( GzipCompression = Compression(oconf.GzipCompression) ) -// Option applies an option to the HTTP client. +// Option applies an option to the Exporter. type Option interface { applyHTTPOption(oconf.Config) oconf.Config } @@ -51,8 +51,8 @@ func asHTTPOptions(opts []Option) []oconf.HTTPOption { return converted } -// RetryConfig defines configuration for retrying batches in case of export -// failure using an exponential backoff. +// RetryConfig defines configuration for retrying the export of metric data +// that failed. type RetryConfig retry.Config type wrappedOption struct { @@ -63,56 +63,123 @@ func (w wrappedOption) applyHTTPOption(cfg oconf.Config) oconf.Config { return w.ApplyHTTPOption(cfg) } -// WithEndpoint allows one to set the address of the collector endpoint that -// the driver will use to send metrics. If unset, it will instead try to use -// the default endpoint (localhost:4318). Note that the endpoint must not -// contain any URL path. +// WithEndpoint sets the target endpoint the Exporter will connect to. This +// endpoint is specified as a host and optional port, no path or scheme should +// be included (see WithInsecure and WithURLPath). +// +// If the OTEL_EXPORTER_OTLP_ENDPOINT or OTEL_EXPORTER_OTLP_METRICS_ENDPOINT +// environment variable is set, and this option is not passed, that variable +// value will be used. If both are set, OTEL_EXPORTER_OTLP_METRICS_ENDPOINT +// will take precedence. +// +// By default, if an environment variable is not set, and this option is not +// passed, "localhost:4318" will be used. func WithEndpoint(endpoint string) Option { return wrappedOption{oconf.WithEndpoint(endpoint)} } -// WithCompression tells the driver to compress the sent data. +// WithCompression sets the compression strategy the Exporter will use to +// compress the HTTP body. +// +// If the OTEL_EXPORTER_OTLP_COMPRESSION or +// OTEL_EXPORTER_OTLP_METRICS_COMPRESSION environment variable is set, and +// this option is not passed, that variable value will be used. That value can +// be either "none" or "gzip". If both are set, +// OTEL_EXPORTER_OTLP_METRICS_COMPRESSION will take precedence. +// +// By default, if an environment variable is not set, and this option is not +// passed, no compression strategy will be used. func WithCompression(compression Compression) Option { return wrappedOption{oconf.WithCompression(oconf.Compression(compression))} } -// WithURLPath allows one to override the default URL path used -// for sending metrics. If unset, default ("/v1/metrics") will be used. +// WithURLPath sets the URL path the Exporter will send requests to. +// +// If the OTEL_EXPORTER_OTLP_ENDPOINT or OTEL_EXPORTER_OTLP_METRICS_ENDPOINT +// environment variable is set, and this option is not passed, the path +// contained in that variable value will be used. If both are set, +// OTEL_EXPORTER_OTLP_METRICS_ENDPOINT will take precedence. +// +// +// By default, if an environment variable is not set, and this option is not +// passed, "/v1/metrics" will be used. func WithURLPath(urlPath string) Option { return wrappedOption{oconf.WithURLPath(urlPath)} } -// WithTLSClientConfig can be used to set up a custom TLS -// configuration for the client used to send payloads to the -// collector. Use it if you want to use a custom certificate. +// WithTLSClientConfig sets the TLS configuration the Exporter will use for +// HTTP requests. +// +// If the OTEL_EXPORTER_OTLP_CERTIFICATE or +// OTEL_EXPORTER_OTLP_METRICS_CERTIFICATE environment variable is set, and +// this option is not passed, that variable value will be used. The value will +// be parsed the filepath of the TLS certificate chain to use. If both are +// set, OTEL_EXPORTER_OTLP_METRICS_CERTIFICATE will take precedence. +// +// By default, if an environment variable is not set, and this option is not +// passed, the system default configuration is used. func WithTLSClientConfig(tlsCfg *tls.Config) Option { return wrappedOption{oconf.WithTLSClientConfig(tlsCfg)} } -// WithInsecure tells the driver to connect to the collector using the -// HTTP scheme, instead of HTTPS. +// WithInsecure disables client transport security for the Exporter's HTTP +// connection. +// +// If the OTEL_EXPORTER_OTLP_ENDPOINT or OTEL_EXPORTER_OTLP_METRICS_ENDPOINT +// environment variable is set, and this option is not passed, that variable +// value will be used to determine client security. If the endpoint has a +// scheme of "http" or "unix" client security will be disabled. If both are +// set, OTEL_EXPORTER_OTLP_METRICS_ENDPOINT will take precedence. +// +// By default, if an environment variable is not set, and this option is not +// passed, client security will be used. func WithInsecure() Option { return wrappedOption{oconf.WithInsecure()} } -// WithHeaders allows one to tell the driver to send additional HTTP -// headers with the payloads. Specifying headers like Content-Length, -// Content-Encoding and Content-Type may result in a broken driver. +// WithHeaders will send the provided headers with each HTTP requests. +// +// If the OTEL_EXPORTER_OTLP_HEADERS or OTEL_EXPORTER_OTLP_METRICS_HEADERS +// environment variable is set, and this option is not passed, that variable +// value will be used. The value will be parsed as a list of key value pairs. +// These pairs are expected to be in the W3C Correlation-Context format +// without additional semi-colon delimited metadata (i.e. "k1=v1,k2=v2"). If +// both are set, OTEL_EXPORTER_OTLP_METRICS_HEADERS will take precedence. +// +// By default, if an environment variable is not set, and this option is not +// passed, no user headers will be set. func WithHeaders(headers map[string]string) Option { return wrappedOption{oconf.WithHeaders(headers)} } -// WithTimeout tells the driver the max waiting time for the backend to process -// each metrics batch. If unset, the default will be 10 seconds. +// WithTimeout sets the max amount of time an Exporter will attempt an export. +// +// This takes precedence over any retry settings defined by WithRetry. Once +// this time limit has been reached the export is abandoned and the metric +// data is dropped. +// +// If the OTEL_EXPORTER_OTLP_TIMEOUT or OTEL_EXPORTER_OTLP_METRICS_TIMEOUT +// environment variable is set, and this option is not passed, that variable +// value will be used. The value will be parsed as an integer representing the +// timeout in milliseconds. If both are set, +// OTEL_EXPORTER_OTLP_METRICS_TIMEOUT will take precedence. +// +// By default, if an environment variable is not set, and this option is not +// passed, a timeout of 10 seconds will be used. func WithTimeout(duration time.Duration) Option { return wrappedOption{oconf.WithTimeout(duration)} } -// WithRetry configures the retry policy for transient errors that may occurs -// when exporting traces. An exponential back-off algorithm is used to ensure -// endpoints are not overwhelmed with retries. If unset, the default retry -// policy will retry after 5 seconds and increase exponentially after each -// error for a total of 1 minute. +// WithRetry sets the retry policy for transient retryable errors that are +// returned by the target endpoint. +// +// If the target endpoint responds with not only a retryable error, but +// explicitly returns a backoff time in the response, that time will take +// precedence over these settings. +// +// If unset, the default retry policy will be used. It will retry the export +// 5 seconds after receiving a retryable error and increase exponentially +// after each error for no more than a total time of 1 minute. func WithRetry(rc RetryConfig) Option { return wrappedOption{oconf.WithRetry(retry.Config(rc))} } From 89c696ad6af7f9c43a617efd45cf3bbee7e5aa3a Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Wed, 17 Aug 2022 15:19:57 -0700 Subject: [PATCH 28/34] Fix lint --- .github/dependabot.yml | 9 +++++++++ exporters/otlp/otlpmetric/otlpmetrichttp/go.mod | 4 ++-- exporters/otlp/otlpmetric/otlpmetrichttp/go.sum | 1 - 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index b3b46d244ac..93558b29ba7 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -109,6 +109,15 @@ updates: schedule: interval: weekly day: sunday + - package-ecosystem: gomod + directory: /exporters/otlp/otlpmetric/otlpmetrichttp + labels: + - dependencies + - go + - Skip Changelog + schedule: + interval: weekly + day: sunday - package-ecosystem: gomod directory: /exporters/otlp/otlptrace labels: diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/go.mod b/exporters/otlp/otlpmetric/otlpmetrichttp/go.mod index ae07225ca34..93355aa6721 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/go.mod +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/go.mod @@ -6,7 +6,7 @@ require ( github.com/stretchr/testify v1.7.1 go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.9.0 go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.31.0 - go.opentelemetry.io/otel/sdk v1.9.0 + go.opentelemetry.io/otel/sdk/metric v0.31.0 go.opentelemetry.io/proto/otlp v0.18.0 google.golang.org/protobuf v1.28.0 ) @@ -21,7 +21,7 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect go.opentelemetry.io/otel v1.9.0 // indirect go.opentelemetry.io/otel/metric v0.31.0 // indirect - go.opentelemetry.io/otel/sdk/metric v0.31.0 // indirect + go.opentelemetry.io/otel/sdk v1.9.0 // indirect go.opentelemetry.io/otel/trace v1.9.0 // indirect golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 // indirect golang.org/x/sys v0.0.0-20210510120138-977fb7262007 // indirect diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/go.sum b/exporters/otlp/otlpmetric/otlpmetrichttp/go.sum index aa3f8bac4ee..1c39dbea853 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/go.sum +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/go.sum @@ -35,7 +35,6 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4= github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= From 1ae0ea91a3be1aac5b3e07999106426e10e7f175 Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Thu, 25 Aug 2022 16:23:04 -0700 Subject: [PATCH 29/34] Fix lint errors --- exporters/otlp/otlpmetric/otlpmetrichttp/config.go | 1 - exporters/otlp/otlpmetric/otlpmetrichttp/go.mod | 2 +- exporters/otlp/otlpmetric/otlpmetrichttp/go.sum | 4 ++-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/config.go b/exporters/otlp/otlpmetric/otlpmetrichttp/config.go index 77b8cbd6e5d..6228b1f7fa2 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/config.go +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/config.go @@ -100,7 +100,6 @@ func WithCompression(compression Compression) Option { // contained in that variable value will be used. If both are set, // OTEL_EXPORTER_OTLP_METRICS_ENDPOINT will take precedence. // -// // By default, if an environment variable is not set, and this option is not // passed, "/v1/metrics" will be used. func WithURLPath(urlPath string) Option { diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/go.mod b/exporters/otlp/otlpmetric/otlpmetrichttp/go.mod index 93355aa6721..30d18497da0 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/go.mod +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/go.mod @@ -7,7 +7,7 @@ require ( go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.9.0 go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.31.0 go.opentelemetry.io/otel/sdk/metric v0.31.0 - go.opentelemetry.io/proto/otlp v0.18.0 + go.opentelemetry.io/proto/otlp v0.19.0 google.golang.org/protobuf v1.28.0 ) diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/go.sum b/exporters/otlp/otlpmetric/otlpmetrichttp/go.sum index 1c39dbea853..c8a15d98b71 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/go.sum +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/go.sum @@ -161,8 +161,8 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.opentelemetry.io/proto/otlp v0.18.0 h1:W5hyXNComRa23tGpKwG+FRAc4rfF6ZUg1JReK+QHS80= -go.opentelemetry.io/proto/otlp v0.18.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= +go.opentelemetry.io/proto/otlp v0.19.0 h1:IVN6GR+mhC4s5yfcTbmzHYODqvWAp3ZedA2SJPI1Nnw= +go.opentelemetry.io/proto/otlp v0.19.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= From 2df85c572eab239fda3b59de49b87905a9327729 Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Thu, 25 Aug 2022 16:27:47 -0700 Subject: [PATCH 30/34] Revert New to accept a context --- exporters/otlp/otlpmetric/otlpmetrichttp/client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/client.go b/exporters/otlp/otlpmetric/otlpmetrichttp/client.go index 1686bc692a4..6dca0fed336 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/client.go +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/client.go @@ -42,7 +42,7 @@ import ( // New returns an OpenTelemetry metric Exporter. The Exporter can be used with // a PeriodicReader to export OpenTelemetry metric data to an OTLP receiving // endpoint using protobufs over HTTP. -func New(opts ...Option) (metric.Exporter, error) { +func New(_ context.Context, opts ...Option) (metric.Exporter, error) { c, err := newClient(opts...) if err != nil { return nil, err From 8515bd191663ea287c85774b9f15e899199e29e0 Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Thu, 25 Aug 2022 16:28:09 -0700 Subject: [PATCH 31/34] Add example test --- .../otlpmetric/otlpmetrichttp/example_test.go | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 exporters/otlp/otlpmetric/otlpmetrichttp/example_test.go diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/example_test.go b/exporters/otlp/otlpmetric/otlpmetrichttp/example_test.go new file mode 100644 index 00000000000..8cae38d0ef7 --- /dev/null +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/example_test.go @@ -0,0 +1,45 @@ +// 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. + +//go:build go1.18 +// +build go1.18 + +package otlpmetrichttp_test + +import ( + "context" + + "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp" + "go.opentelemetry.io/otel/metric/global" + "go.opentelemetry.io/otel/sdk/metric" +) + +func Example() { + ctx := context.Background() + exp, err := otlpmetrichttp.New(ctx) + if err != nil { + panic(err) + } + + meterProvider := metric.NewMeterProvider(metric.WithReader(metric.NewPeriodicReader(exp))) + defer func() { + if err := meterProvider.Shutdown(ctx); err != nil { + panic(err) + } + }() + global.SetMeterProvider(meterProvider) + + // From here, the meterProvider can be used by instrumentation to collect + // telemetry. +} From 125c156b3452d327c9fd98a4d956d2fc14064158 Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Thu, 25 Aug 2022 16:32:35 -0700 Subject: [PATCH 32/34] Update pkg docs --- exporters/otlp/otlpmetric/otlpmetrichttp/doc.go | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/doc.go b/exporters/otlp/otlpmetric/otlpmetrichttp/doc.go index d096388320d..a49e2465171 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/doc.go +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/doc.go @@ -12,12 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -/* -Package otlpmetrichttp provides a client that sends metrics to the collector -using HTTP with binary protobuf payloads. - -This package is currently in a pre-GA phase. Backwards incompatible changes -may be introduced in subsequent minor version releases as we work to track the -evolving OpenTelemetry specification and user feedback. -*/ +// Package otlpmetrichttp provides an otlpmetric.Exporter that communicates +// with an OTLP receiving endpoint using protobuf encoded metric data over +// HTTP. package otlpmetrichttp // import "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp" From cfe9f69ed7a0867c8bf0d07e661ca5c7b24bc6ce Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Thu, 25 Aug 2022 16:39:19 -0700 Subject: [PATCH 33/34] go mod tidy --- exporters/otlp/otlpmetric/otlpmetrichttp/go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/go.mod b/exporters/otlp/otlpmetric/otlpmetrichttp/go.mod index 30d18497da0..63e80a53002 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/go.mod +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/go.mod @@ -6,6 +6,7 @@ require ( github.com/stretchr/testify v1.7.1 go.opentelemetry.io/otel/exporters/otlp/internal/retry v1.9.0 go.opentelemetry.io/otel/exporters/otlp/otlpmetric v0.31.0 + go.opentelemetry.io/otel/metric v0.31.0 go.opentelemetry.io/otel/sdk/metric v0.31.0 go.opentelemetry.io/proto/otlp v0.19.0 google.golang.org/protobuf v1.28.0 @@ -20,7 +21,6 @@ require ( github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect go.opentelemetry.io/otel v1.9.0 // indirect - go.opentelemetry.io/otel/metric v0.31.0 // indirect go.opentelemetry.io/otel/sdk v1.9.0 // indirect go.opentelemetry.io/otel/trace v1.9.0 // indirect golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 // indirect From 2ef5232851a2fbe4b3cae76b1b540e36ad3dac70 Mon Sep 17 00:00:00 2001 From: Tyler Yahn Date: Fri, 2 Sep 2022 11:10:26 -0700 Subject: [PATCH 34/34] Use url.URL to form HTTP request URL --- exporters/otlp/otlpmetric/otlpmetrichttp/client.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/client.go b/exporters/otlp/otlpmetric/otlpmetrichttp/client.go index 6dca0fed336..788c15c0615 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/client.go +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/client.go @@ -25,6 +25,7 @@ import ( "io" "net" "net/http" + "net/url" "strconv" "sync" "time" @@ -89,13 +90,16 @@ func newClient(opts ...Option) (otlpmetric.Client, error) { httpClient.Transport = transport } - format := "https://%s%s" + u := &url.URL{ + Scheme: "https", + Host: cfg.Metrics.Endpoint, + Path: cfg.Metrics.URLPath, + } if cfg.Metrics.Insecure { - format = "http://%s%s" + u.Scheme = "http" } - rawURL := fmt.Sprintf(format, cfg.Metrics.Endpoint, cfg.Metrics.URLPath) // Body is set when this is cloned during upload. - req, err := http.NewRequest(http.MethodPost, rawURL, http.NoBody) + req, err := http.NewRequest(http.MethodPost, u.String(), http.NoBody) if err != nil { return nil, err }