From 6e92163d6adf91ec77fa41c155c557da6e078563 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Paj=C4=85k?= Date: Mon, 22 Apr 2024 13:07:29 +0200 Subject: [PATCH] otlpmetrichttp: Use go.opentelemetry.io/proto/slim/otlp (#5222) --- CHANGELOG.md | 1 + .../otlpmetric/otlpmetricgrpc/internal/gen.go | 19 +- .../internal/oconf/envconfig.go | 43 +- .../internal/oconf/envconfig_test.go | 3 - .../otlpmetricgrpc/internal/oconf/options.go | 177 ++---- .../internal/oconf/options_test.go | 189 ++---- .../internal/otest/collector.go | 313 +--------- .../otlp/otlpmetric/otlpmetrichttp/client.go | 4 +- .../otlpmetric/otlpmetrichttp/client_test.go | 2 +- .../otlpmetric/otlpmetrichttp/exporter.go | 2 +- .../otlp/otlpmetric/otlpmetrichttp/go.mod | 11 +- .../otlp/otlpmetric/otlpmetrichttp/go.sum | 23 +- .../otlpmetric/otlpmetrichttp/internal/gen.go | 19 +- .../internal/oconf/envconfig.go | 37 +- .../internal/oconf/envconfig_test.go | 7 +- .../otlpmetrichttp/internal/oconf/options.go | 217 ++----- .../internal/oconf/options_test.go | 194 ++---- .../otlpmetrichttp/internal/otest/client.go | 8 +- .../internal/otest/client_test.go | 4 +- .../internal/otest/collector.go | 98 +-- .../internal/transform/attribute.go | 2 +- .../internal/transform/attribute_test.go | 2 +- .../internal/transform/error.go | 2 +- .../internal/transform/metricdata.go | 6 +- .../internal/transform/metricdata_test.go | 6 +- .../otlp/otlpmetric/oconf/envconfig.go.tmpl | 210 ------- .../otlpmetric/oconf/envconfig_test.go.tmpl | 165 ----- .../otlp/otlpmetric/oconf/options.go.tmpl | 376 ----------- .../otlpmetric/oconf/options_test.go.tmpl | 583 ------------------ .../otlp/otlpmetric/otest/client.go.tmpl | 8 +- .../otlp/otlpmetric/otest/client_test.go.tmpl | 4 +- .../otlp/otlpmetric/otest/collector.go.tmpl | 451 -------------- .../otlpmetric/transform/attribute.go.tmpl | 2 +- .../transform/attribute_test.go.tmpl | 2 +- .../otlp/otlpmetric/transform/error.go.tmpl | 2 +- .../otlpmetric/transform/metricdata.go.tmpl | 6 +- .../transform/metricdata_test.go.tmpl | 6 +- 37 files changed, 282 insertions(+), 2922 deletions(-) delete mode 100644 internal/shared/otlp/otlpmetric/oconf/envconfig.go.tmpl delete mode 100644 internal/shared/otlp/otlpmetric/oconf/envconfig_test.go.tmpl delete mode 100644 internal/shared/otlp/otlpmetric/oconf/options.go.tmpl delete mode 100644 internal/shared/otlp/otlpmetric/oconf/options_test.go.tmpl delete mode 100644 internal/shared/otlp/otlpmetric/otest/collector.go.tmpl diff --git a/CHANGELOG.md b/CHANGELOG.md index 99b8a28907d..4612342d96f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Update `go.opentelemetry.io/proto/otlp` from v1.1.0 to v1.2.0. (#5177) - Improve performance of baggage member character validation in `go.opentelemetry.io/otel/baggage`. (#5214) +- `go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp` no longer depends on `google.golang.org/grpc`. (#5222) ## [1.25.0/0.47.0/0.0.8/0.1.0-alpha] 2024-04-05 diff --git a/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/gen.go b/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/gen.go index 95e2f4ba3b0..57d4c7ada52 100644 --- a/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/gen.go +++ b/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/gen.go @@ -12,20 +12,15 @@ package internal // import "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/o //go:generate gotmpl --body=../../../../../internal/shared/otlp/envconfig/envconfig.go.tmpl "--data={}" --out=envconfig/envconfig.go //go:generate gotmpl --body=../../../../../internal/shared/otlp/envconfig/envconfig_test.go.tmpl "--data={}" --out=envconfig/envconfig_test.go -//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/oconf/envconfig.go.tmpl "--data={\"envconfigImportPath\": \"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/envconfig\"}" --out=oconf/envconfig.go -//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/oconf/envconfig_test.go.tmpl "--data={}" --out=oconf/envconfig_test.go -//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/oconf/options.go.tmpl "--data={\"retryImportPath\": \"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/retry\"}" --out=oconf/options.go -//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/oconf/options_test.go.tmpl "--data={\"envconfigImportPath\": \"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/envconfig\"}" --out=oconf/options_test.go //go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/oconf/optiontypes.go.tmpl "--data={}" --out=oconf/optiontypes.go //go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/oconf/tls.go.tmpl "--data={}" --out=oconf/tls.go -//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/otest/client.go.tmpl "--data={}" --out=otest/client.go -//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/otest/client_test.go.tmpl "--data={\"internalImportPath\": \"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/internal\"}" --out=otest/client_test.go -//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/otest/collector.go.tmpl "--data={\"oconfImportPath\": \"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/oconf\"}" --out=otest/collector.go +//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/otest/client.go.tmpl "--data={\"protoImportPrefix\": \"go.opentelemetry.io/proto\"}" --out=otest/client.go +//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/otest/client_test.go.tmpl "--data={\"protoImportPrefix\": \"go.opentelemetry.io/proto\", \"internalImportPath\": \"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/internal\"}" --out=otest/client_test.go -//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/transform/attribute.go.tmpl "--data={}" --out=transform/attribute.go -//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/transform/attribute_test.go.tmpl "--data={}" --out=transform/attribute_test.go -//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/transform/error.go.tmpl "--data={}" --out=transform/error.go +//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/transform/attribute.go.tmpl "--data={\"protoImportPrefix\": \"go.opentelemetry.io/proto\"}" --out=transform/attribute.go +//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/transform/attribute_test.go.tmpl "--data={\"protoImportPrefix\": \"go.opentelemetry.io/proto\"}" --out=transform/attribute_test.go +//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/transform/error.go.tmpl "--data={\"protoImportPrefix\": \"go.opentelemetry.io/proto\"}" --out=transform/error.go //go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/transform/error_test.go.tmpl "--data={}" --out=transform/error_test.go -//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/transform/metricdata.go.tmpl "--data={}" --out=transform/metricdata.go -//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/transform/metricdata_test.go.tmpl "--data={}" --out=transform/metricdata_test.go +//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/transform/metricdata.go.tmpl "--data={\"protoImportPrefix\": \"go.opentelemetry.io/proto\"}" --out=transform/metricdata.go +//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/transform/metricdata_test.go.tmpl "--data={\"protoImportPrefix\": \"go.opentelemetry.io/proto\"}" --out=transform/metricdata_test.go diff --git a/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/oconf/envconfig.go b/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/oconf/envconfig.go index 7ae53f2d181..860c806b462 100644 --- a/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/oconf/envconfig.go +++ b/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/oconf/envconfig.go @@ -1,6 +1,3 @@ -// Code created by gotmpl. DO NOT MODIFY. -// source: internal/shared/otlp/otlpmetric/oconf/envconfig.go.tmpl - // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 @@ -37,46 +34,18 @@ func ApplyGRPCEnvConfigs(cfg Config) Config { return cfg } -// ApplyHTTPEnvConfigs applies the env configurations for HTTP. -func ApplyHTTPEnvConfigs(cfg Config) Config { - opts := getOptionsFromEnv() - for _, opt := range opts { - cfg = opt.ApplyHTTPOption(cfg) - } - return cfg -} - -func getOptionsFromEnv() []GenericOption { - opts := []GenericOption{} +func getOptionsFromEnv() []GRPCOption { + opts := []GRPCOption{} tlsConf := &tls.Config{} DefaultEnvOptionsReader.Apply( envconfig.WithURL("ENDPOINT", func(u *url.URL) { opts = append(opts, withEndpointScheme(u)) - opts = append(opts, newSplitOption(func(cfg Config) Config { - cfg.Metrics.Endpoint = u.Host - // For OTLP/HTTP endpoint URLs without a per-signal - // configuration, the passed endpoint is used as a base URL - // and the signals are sent to these paths relative to that. - cfg.Metrics.URLPath = path.Join(u.Path, DefaultMetricsPath) - return cfg - }, withEndpointForGRPC(u))) + opts = append(opts, NewGRPCOption(withEndpointForGRPC(u))) }), envconfig.WithURL("METRICS_ENDPOINT", func(u *url.URL) { opts = append(opts, withEndpointScheme(u)) - opts = append(opts, newSplitOption(func(cfg Config) Config { - cfg.Metrics.Endpoint = u.Host - // For endpoint URLs for OTLP/HTTP per-signal variables, the - // URL MUST be used as-is without any modification. The only - // exception is that if an URL contains no path part, the root - // path / MUST be used. - path := u.Path - if path == "" { - path = "/" - } - cfg.Metrics.URLPath = path - return cfg - }, withEndpointForGRPC(u))) + opts = append(opts, NewGRPCOption(withEndpointForGRPC(u))) }), envconfig.WithCertPool("CERTIFICATE", func(p *x509.CertPool) { tlsConf.RootCAs = p }), envconfig.WithCertPool("METRICS_CERTIFICATE", func(p *x509.CertPool) { tlsConf.RootCAs = p }), @@ -121,7 +90,7 @@ func WithEnvCompression(n string, fn func(Compression)) func(e *envconfig.EnvOpt } } -func withEndpointScheme(u *url.URL) GenericOption { +func withEndpointScheme(u *url.URL) GRPCOption { switch strings.ToLower(u.Scheme) { case "http", "unix": return WithInsecure() @@ -131,7 +100,7 @@ func withEndpointScheme(u *url.URL) GenericOption { } // revive:disable-next-line:flag-parameter -func withInsecure(b bool) GenericOption { +func withInsecure(b bool) GRPCOption { if b { return WithInsecure() } diff --git a/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/oconf/envconfig_test.go b/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/oconf/envconfig_test.go index 5c621d7b47e..ee90ff821b1 100644 --- a/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/oconf/envconfig_test.go +++ b/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/oconf/envconfig_test.go @@ -1,6 +1,3 @@ -// Code created by gotmpl. DO NOT MODIFY. -// source: internal/shared/otlp/otlpmetric/oconf/envconfig_test.go.tmpl - // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 diff --git a/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/oconf/options.go b/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/oconf/options.go index b6ed9a2bb65..3c17f44190f 100644 --- a/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/oconf/options.go +++ b/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/oconf/options.go @@ -1,6 +1,3 @@ -// Code created by gotmpl. DO NOT MODIFY. -// source: internal/shared/otlp/otlpmetric/oconf/options.go.tmpl - // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 @@ -47,6 +44,7 @@ type ( // This type is compatible with `http.Transport.Proxy` and can be used to set a custom proxy function to the OTLP HTTP client. HTTPTransportProxyFunc func(*http.Request) (*url.URL, error) + // SignalConfig represents signal specific configuration. SignalConfig struct { Endpoint string Insecure bool @@ -65,8 +63,8 @@ type ( Proxy HTTPTransportProxyFunc } + // Config represents exporter configuration. Config struct { - // Signal specific configurations Metrics SignalConfig RetryConfig retry.Config @@ -79,29 +77,6 @@ type ( } ) -// NewHTTPConfig returns a new Config with all settings applied from opts and -// any unset setting using the default HTTP config values. -func NewHTTPConfig(opts ...HTTPOption) Config { - cfg := Config{ - Metrics: SignalConfig{ - Endpoint: fmt.Sprintf("%s:%d", DefaultCollectorHost, DefaultCollectorHTTPPort), - URLPath: DefaultMetricsPath, - Compression: NoCompression, - Timeout: DefaultTimeout, - - TemporalitySelector: metric.DefaultTemporalitySelector, - AggregationSelector: metric.DefaultAggregationSelector, - }, - RetryConfig: retry.DefaultConfig, - } - cfg = ApplyHTTPEnvConfigs(cfg) - for _, opt := range opts { - cfg = opt.ApplyHTTPOption(cfg) - } - cfg.Metrics.URLPath = cleanPath(cfg.Metrics.URLPath, DefaultMetricsPath) - return cfg -} - // cleanPath returns a path with all spaces trimmed and all redundancies // removed. If urlPath is empty or cleaning it results in an empty string, // defaultPath is returned instead. @@ -164,93 +139,14 @@ func NewGRPCConfig(opts ...GRPCOption) Config { return cfg } -type ( - // GenericOption applies an option to the HTTP or gRPC driver. - GenericOption interface { - ApplyHTTPOption(Config) Config - ApplyGRPCOption(Config) Config - - // A private method to prevent users implementing the - // interface and so future additions to it will not - // violate compatibility. - private() - } - - // HTTPOption applies an option to the HTTP driver. - HTTPOption interface { - ApplyHTTPOption(Config) Config +// GRPCOption applies an option to the gRPC driver. +type GRPCOption interface { + ApplyGRPCOption(Config) Config - // A private method to prevent users implementing the - // interface and so future additions to it will not - // violate compatibility. - private() - } - - // GRPCOption applies an option to the gRPC driver. - GRPCOption interface { - ApplyGRPCOption(Config) Config - - // A private method to prevent users implementing the - // interface and so future additions to it will not - // violate compatibility. - private() - } -) - -// genericOption is an option that applies the same logic -// for both gRPC and HTTP. -type genericOption struct { - fn func(Config) Config -} - -func (g *genericOption) ApplyGRPCOption(cfg Config) Config { - return g.fn(cfg) -} - -func (g *genericOption) ApplyHTTPOption(cfg Config) Config { - return g.fn(cfg) -} - -func (genericOption) private() {} - -func newGenericOption(fn func(cfg Config) Config) GenericOption { - return &genericOption{fn: fn} -} - -// splitOption is an option that applies different logics -// for gRPC and HTTP. -type splitOption struct { - httpFn func(Config) Config - grpcFn func(Config) Config -} - -func (g *splitOption) ApplyGRPCOption(cfg Config) Config { - return g.grpcFn(cfg) -} - -func (g *splitOption) ApplyHTTPOption(cfg Config) Config { - return g.httpFn(cfg) -} - -func (splitOption) private() {} - -func newSplitOption(httpFn func(cfg Config) Config, grpcFn func(cfg Config) Config) GenericOption { - return &splitOption{httpFn: httpFn, grpcFn: grpcFn} -} - -// httpOption is an option that is only applied to the HTTP driver. -type httpOption struct { - fn func(Config) Config -} - -func (h *httpOption) ApplyHTTPOption(cfg Config) Config { - return h.fn(cfg) -} - -func (httpOption) private() {} - -func NewHTTPOption(fn func(cfg Config) Config) HTTPOption { - return &httpOption{fn: fn} + // A private method to prevent users implementing the + // interface and so future additions to it will not + // violate compatibility. + private() } // grpcOption is an option that is only applied to the gRPC driver. @@ -270,15 +166,15 @@ func NewGRPCOption(fn func(cfg Config) Config) GRPCOption { // Generic Options -func WithEndpoint(endpoint string) GenericOption { - return newGenericOption(func(cfg Config) Config { +func WithEndpoint(endpoint string) GRPCOption { + return NewGRPCOption(func(cfg Config) Config { cfg.Metrics.Endpoint = endpoint return cfg }) } -func WithEndpointURL(v string) GenericOption { - return newGenericOption(func(cfg Config) Config { +func WithEndpointURL(v string) GRPCOption { + return NewGRPCOption(func(cfg Config) Config { u, err := url.Parse(v) if err != nil { global.Error(err, "otlpmetric: parse endpoint url", "url", v) @@ -295,81 +191,78 @@ func WithEndpointURL(v string) GenericOption { }) } -func WithCompression(compression Compression) GenericOption { - return newGenericOption(func(cfg Config) Config { +func WithCompression(compression Compression) GRPCOption { + return NewGRPCOption(func(cfg Config) Config { cfg.Metrics.Compression = compression return cfg }) } -func WithURLPath(urlPath string) GenericOption { - return newGenericOption(func(cfg Config) Config { +func WithURLPath(urlPath string) GRPCOption { + return NewGRPCOption(func(cfg Config) Config { cfg.Metrics.URLPath = urlPath return cfg }) } -func WithRetry(rc retry.Config) GenericOption { - return newGenericOption(func(cfg Config) Config { +func WithRetry(rc retry.Config) GRPCOption { + return NewGRPCOption(func(cfg Config) Config { cfg.RetryConfig = rc return cfg }) } -func WithTLSClientConfig(tlsCfg *tls.Config) GenericOption { - return newSplitOption(func(cfg Config) Config { - cfg.Metrics.TLSCfg = tlsCfg.Clone() - return cfg - }, func(cfg Config) Config { +func WithTLSClientConfig(tlsCfg *tls.Config) GRPCOption { + return NewGRPCOption(func(cfg Config) Config { cfg.Metrics.GRPCCredentials = credentials.NewTLS(tlsCfg) return cfg }) } -func WithInsecure() GenericOption { - return newGenericOption(func(cfg Config) Config { +func WithInsecure() GRPCOption { + return NewGRPCOption(func(cfg Config) Config { cfg.Metrics.Insecure = true return cfg }) } -func WithSecure() GenericOption { - return newGenericOption(func(cfg Config) Config { +func WithSecure() GRPCOption { + return NewGRPCOption(func(cfg Config) Config { cfg.Metrics.Insecure = false return cfg }) } -func WithHeaders(headers map[string]string) GenericOption { - return newGenericOption(func(cfg Config) Config { +func WithHeaders(headers map[string]string) GRPCOption { + return NewGRPCOption(func(cfg Config) Config { cfg.Metrics.Headers = headers return cfg }) } -func WithTimeout(duration time.Duration) GenericOption { - return newGenericOption(func(cfg Config) Config { +func WithTimeout(duration time.Duration) GRPCOption { + return NewGRPCOption(func(cfg Config) Config { cfg.Metrics.Timeout = duration return cfg }) } -func WithTemporalitySelector(selector metric.TemporalitySelector) GenericOption { - return newGenericOption(func(cfg Config) Config { +func WithTemporalitySelector(selector metric.TemporalitySelector) GRPCOption { + return NewGRPCOption(func(cfg Config) Config { cfg.Metrics.TemporalitySelector = selector return cfg }) } -func WithAggregationSelector(selector metric.AggregationSelector) GenericOption { - return newGenericOption(func(cfg Config) Config { +func WithAggregationSelector(selector metric.AggregationSelector) GRPCOption { + return NewGRPCOption(func(cfg Config) Config { cfg.Metrics.AggregationSelector = selector return cfg }) } -func WithProxy(pf HTTPTransportProxyFunc) GenericOption { - return newGenericOption(func(cfg Config) Config { +func WithProxy(pf HTTPTransportProxyFunc) GRPCOption { + return NewGRPCOption(func(cfg Config) Config { cfg.Metrics.Proxy = pf return cfg }) diff --git a/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/oconf/options_test.go b/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/oconf/options_test.go index a24a9544556..9b701950be8 100644 --- a/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/oconf/options_test.go +++ b/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/oconf/options_test.go @@ -1,6 +1,3 @@ -// Code created by gotmpl. DO NOT MODIFY. -// source: internal/shared/otlp/otlpmetric/oconf/options_test.go.tmpl - // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 @@ -64,19 +61,15 @@ func TestConfigs(t *testing.T) { tests := []struct { name string - opts []GenericOption + opts []GRPCOption env env fileReader fileReader - asserts func(t *testing.T, c *Config, grpcOption bool) + asserts func(t *testing.T, c *Config) }{ { name: "Test default configs", - asserts: func(t *testing.T, c *Config, grpcOption bool) { - if grpcOption { - assert.Equal(t, "localhost:4317", c.Metrics.Endpoint) - } else { - assert.Equal(t, "localhost:4318", c.Metrics.Endpoint) - } + asserts: func(t *testing.T, c *Config) { + assert.Equal(t, "localhost:4317", c.Metrics.Endpoint) assert.Equal(t, NoCompression, c.Metrics.Compression) assert.Equal(t, map[string]string(nil), c.Metrics.Headers) assert.Equal(t, 10*time.Second, c.Metrics.Timeout) @@ -86,19 +79,19 @@ func TestConfigs(t *testing.T) { // Endpoint Tests { name: "Test With Endpoint", - opts: []GenericOption{ + opts: []GRPCOption{ WithEndpoint("someendpoint"), }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { + asserts: func(t *testing.T, c *Config) { assert.Equal(t, "someendpoint", c.Metrics.Endpoint) }, }, { name: "Test With Endpoint URL", - opts: []GenericOption{ + opts: []GRPCOption{ WithEndpointURL("http://someendpoint/somepath"), }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { + asserts: func(t *testing.T, c *Config) { assert.Equal(t, "someendpoint", c.Metrics.Endpoint) assert.Equal(t, "/somepath", c.Metrics.URLPath) assert.Equal(t, true, c.Metrics.Insecure) @@ -106,10 +99,10 @@ func TestConfigs(t *testing.T) { }, { name: "Test With Secure Endpoint URL", - opts: []GenericOption{ + opts: []GRPCOption{ WithEndpointURL("https://someendpoint/somepath"), }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { + asserts: func(t *testing.T, c *Config) { assert.Equal(t, "someendpoint", c.Metrics.Endpoint) assert.Equal(t, "/somepath", c.Metrics.URLPath) assert.Equal(t, false, c.Metrics.Insecure) @@ -117,15 +110,11 @@ func TestConfigs(t *testing.T) { }, { name: "Test With Invalid Endpoint URL", - opts: []GenericOption{ + opts: []GRPCOption{ WithEndpointURL("%invalid"), }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { - if grpcOption { - assert.Equal(t, "localhost:4317", c.Metrics.Endpoint) - } else { - assert.Equal(t, "localhost:4318", c.Metrics.Endpoint) - } + asserts: func(t *testing.T, c *Config) { + assert.Equal(t, "localhost:4317", c.Metrics.Endpoint) assert.Equal(t, "/v1/metrics", c.Metrics.URLPath) }, }, @@ -134,14 +123,9 @@ func TestConfigs(t *testing.T) { env: map[string]string{ "OTEL_EXPORTER_OTLP_ENDPOINT": "https://env.endpoint/prefix", }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { + asserts: func(t *testing.T, c *Config) { assert.False(t, c.Metrics.Insecure) - if grpcOption { - assert.Equal(t, "env.endpoint/prefix", c.Metrics.Endpoint) - } else { - assert.Equal(t, "env.endpoint", c.Metrics.Endpoint) - assert.Equal(t, "/prefix/v1/metrics", c.Metrics.URLPath) - } + assert.Equal(t, "env.endpoint/prefix", c.Metrics.Endpoint) }, }, { @@ -150,23 +134,20 @@ func TestConfigs(t *testing.T) { "OTEL_EXPORTER_OTLP_ENDPOINT": "https://overrode.by.signal.specific/env/var", "OTEL_EXPORTER_OTLP_METRICS_ENDPOINT": "http://env.metrics.endpoint", }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { + asserts: func(t *testing.T, c *Config) { assert.True(t, c.Metrics.Insecure) assert.Equal(t, "env.metrics.endpoint", c.Metrics.Endpoint) - if !grpcOption { - assert.Equal(t, "/", c.Metrics.URLPath) - } }, }, { name: "Test Mixed Environment and With Endpoint", - opts: []GenericOption{ + opts: []GRPCOption{ WithEndpoint("metrics_endpoint"), }, env: map[string]string{ "OTEL_EXPORTER_OTLP_ENDPOINT": "env_endpoint", }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { + asserts: func(t *testing.T, c *Config) { assert.Equal(t, "metrics_endpoint", c.Metrics.Endpoint) }, }, @@ -175,7 +156,7 @@ func TestConfigs(t *testing.T) { env: map[string]string{ "OTEL_EXPORTER_OTLP_ENDPOINT": "http://env_endpoint", }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { + asserts: func(t *testing.T, c *Config) { assert.Equal(t, "env_endpoint", c.Metrics.Endpoint) assert.Equal(t, true, c.Metrics.Insecure) }, @@ -185,7 +166,7 @@ func TestConfigs(t *testing.T) { env: map[string]string{ "OTEL_EXPORTER_OTLP_ENDPOINT": " http://env_endpoint ", }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { + asserts: func(t *testing.T, c *Config) { assert.Equal(t, "env_endpoint", c.Metrics.Endpoint) assert.Equal(t, true, c.Metrics.Insecure) }, @@ -195,7 +176,7 @@ func TestConfigs(t *testing.T) { env: map[string]string{ "OTEL_EXPORTER_OTLP_ENDPOINT": "https://env_endpoint", }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { + asserts: func(t *testing.T, c *Config) { assert.Equal(t, "env_endpoint", c.Metrics.Endpoint) assert.Equal(t, false, c.Metrics.Insecure) }, @@ -206,7 +187,7 @@ func TestConfigs(t *testing.T) { "OTEL_EXPORTER_OTLP_ENDPOINT": "HTTPS://overrode_by_signal_specific", "OTEL_EXPORTER_OTLP_METRICS_ENDPOINT": "HtTp://env_metrics_endpoint", }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { + asserts: func(t *testing.T, c *Config) { assert.Equal(t, "env_metrics_endpoint", c.Metrics.Endpoint) assert.Equal(t, true, c.Metrics.Insecure) }, @@ -215,27 +196,18 @@ func TestConfigs(t *testing.T) { // Certificate tests { name: "Test Default Certificate", - asserts: func(t *testing.T, c *Config, grpcOption bool) { - if grpcOption { - assert.NotNil(t, c.Metrics.GRPCCredentials) - } else { - assert.Nil(t, c.Metrics.TLSCfg) - } + asserts: func(t *testing.T, c *Config) { + assert.NotNil(t, c.Metrics.GRPCCredentials) }, }, { name: "Test With Certificate", - opts: []GenericOption{ + opts: []GRPCOption{ WithTLSClientConfig(tlsCert), }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { - if grpcOption { - // TODO: make sure gRPC's credentials actually works - assert.NotNil(t, c.Metrics.GRPCCredentials) - } else { - // nolint:staticcheck // ignoring tlsCert.RootCAs.Subjects is deprecated ERR because cert does not come from SystemCertPool. - assert.Equal(t, tlsCert.RootCAs.Subjects(), c.Metrics.TLSCfg.RootCAs.Subjects()) - } + asserts: func(t *testing.T, c *Config) { + // TODO: make sure gRPC's credentials actually works + assert.NotNil(t, c.Metrics.GRPCCredentials) }, }, { @@ -246,13 +218,8 @@ func TestConfigs(t *testing.T) { fileReader: fileReader{ "cert_path": []byte(WeakCertificate), }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { - if grpcOption { - assert.NotNil(t, c.Metrics.GRPCCredentials) - } else { - // nolint:staticcheck // ignoring tlsCert.RootCAs.Subjects is deprecated ERR because cert does not come from SystemCertPool. - assert.Equal(t, tlsCert.RootCAs.Subjects(), c.Metrics.TLSCfg.RootCAs.Subjects()) - } + asserts: func(t *testing.T, c *Config) { + assert.NotNil(t, c.Metrics.GRPCCredentials) }, }, { @@ -265,48 +232,38 @@ func TestConfigs(t *testing.T) { "cert_path": []byte(WeakCertificate), "invalid_cert": []byte("invalid certificate file."), }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { - if grpcOption { - assert.NotNil(t, c.Metrics.GRPCCredentials) - } else { - // nolint:staticcheck // ignoring tlsCert.RootCAs.Subjects is deprecated ERR because cert does not come from SystemCertPool. - assert.Equal(t, tlsCert.RootCAs.Subjects(), c.Metrics.TLSCfg.RootCAs.Subjects()) - } + asserts: func(t *testing.T, c *Config) { + assert.NotNil(t, c.Metrics.GRPCCredentials) }, }, { name: "Test Mixed Environment and With Certificate", - opts: []GenericOption{}, + opts: []GRPCOption{}, env: map[string]string{ "OTEL_EXPORTER_OTLP_CERTIFICATE": "cert_path", }, fileReader: fileReader{ "cert_path": []byte(WeakCertificate), }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { - if grpcOption { - assert.NotNil(t, c.Metrics.GRPCCredentials) - } else { - // nolint:staticcheck // ignoring tlsCert.RootCAs.Subjects is deprecated ERR because cert does not come from SystemCertPool. - assert.Equal(t, 1, len(c.Metrics.TLSCfg.RootCAs.Subjects())) - } + asserts: func(t *testing.T, c *Config) { + assert.NotNil(t, c.Metrics.GRPCCredentials) }, }, // Headers tests { name: "Test With Headers", - opts: []GenericOption{ + opts: []GRPCOption{ WithHeaders(map[string]string{"h1": "v1"}), }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { + asserts: func(t *testing.T, c *Config) { assert.Equal(t, map[string]string{"h1": "v1"}, c.Metrics.Headers) }, }, { name: "Test Environment Headers", env: map[string]string{"OTEL_EXPORTER_OTLP_HEADERS": "h1=v1,h2=v2"}, - asserts: func(t *testing.T, c *Config, grpcOption bool) { + asserts: func(t *testing.T, c *Config) { assert.Equal(t, map[string]string{"h1": "v1", "h2": "v2"}, c.Metrics.Headers) }, }, @@ -316,17 +273,17 @@ func TestConfigs(t *testing.T) { "OTEL_EXPORTER_OTLP_HEADERS": "overrode_by_signal_specific", "OTEL_EXPORTER_OTLP_METRICS_HEADERS": "h1=v1,h2=v2", }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { + asserts: func(t *testing.T, c *Config) { assert.Equal(t, map[string]string{"h1": "v1", "h2": "v2"}, c.Metrics.Headers) }, }, { name: "Test Mixed Environment and With Headers", env: map[string]string{"OTEL_EXPORTER_OTLP_HEADERS": "h1=v1,h2=v2"}, - opts: []GenericOption{ + opts: []GRPCOption{ WithHeaders(map[string]string{"m1": "mv1"}), }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { + asserts: func(t *testing.T, c *Config) { assert.Equal(t, map[string]string{"m1": "mv1"}, c.Metrics.Headers) }, }, @@ -334,10 +291,10 @@ func TestConfigs(t *testing.T) { // Compression Tests { name: "Test With Compression", - opts: []GenericOption{ + opts: []GRPCOption{ WithCompression(GzipCompression), }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { + asserts: func(t *testing.T, c *Config) { assert.Equal(t, GzipCompression, c.Metrics.Compression) }, }, @@ -346,7 +303,7 @@ func TestConfigs(t *testing.T) { env: map[string]string{ "OTEL_EXPORTER_OTLP_COMPRESSION": "gzip", }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { + asserts: func(t *testing.T, c *Config) { assert.Equal(t, GzipCompression, c.Metrics.Compression) }, }, @@ -355,19 +312,19 @@ func TestConfigs(t *testing.T) { env: map[string]string{ "OTEL_EXPORTER_OTLP_METRICS_COMPRESSION": "gzip", }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { + asserts: func(t *testing.T, c *Config) { assert.Equal(t, GzipCompression, c.Metrics.Compression) }, }, { name: "Test Mixed Environment and With Compression", - opts: []GenericOption{ + opts: []GRPCOption{ WithCompression(NoCompression), }, env: map[string]string{ "OTEL_EXPORTER_OTLP_METRICS_COMPRESSION": "gzip", }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { + asserts: func(t *testing.T, c *Config) { assert.Equal(t, NoCompression, c.Metrics.Compression) }, }, @@ -375,10 +332,10 @@ func TestConfigs(t *testing.T) { // Timeout Tests { name: "Test With Timeout", - opts: []GenericOption{ + opts: []GRPCOption{ WithTimeout(time.Duration(5 * time.Second)), }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { + asserts: func(t *testing.T, c *Config) { assert.Equal(t, 5*time.Second, c.Metrics.Timeout) }, }, @@ -387,7 +344,7 @@ func TestConfigs(t *testing.T) { env: map[string]string{ "OTEL_EXPORTER_OTLP_TIMEOUT": "15000", }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { + asserts: func(t *testing.T, c *Config) { assert.Equal(t, c.Metrics.Timeout, 15*time.Second) }, }, @@ -397,7 +354,7 @@ func TestConfigs(t *testing.T) { "OTEL_EXPORTER_OTLP_TIMEOUT": "15000", "OTEL_EXPORTER_OTLP_METRICS_TIMEOUT": "28000", }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { + asserts: func(t *testing.T, c *Config) { assert.Equal(t, c.Metrics.Timeout, 28*time.Second) }, }, @@ -407,10 +364,10 @@ func TestConfigs(t *testing.T) { "OTEL_EXPORTER_OTLP_TIMEOUT": "15000", "OTEL_EXPORTER_OTLP_METRICS_TIMEOUT": "28000", }, - opts: []GenericOption{ + opts: []GRPCOption{ WithTimeout(5 * time.Second), }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { + asserts: func(t *testing.T, c *Config) { assert.Equal(t, c.Metrics.Timeout, 5*time.Second) }, }, @@ -418,10 +375,10 @@ func TestConfigs(t *testing.T) { // Temporality Selector Tests { name: "WithTemporalitySelector", - opts: []GenericOption{ + opts: []GRPCOption{ WithTemporalitySelector(deltaSelector), }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { + asserts: func(t *testing.T, c *Config) { // Function value comparisons are disallowed, test non-default // behavior of a TemporalitySelector here to ensure our "catch // all" was set. @@ -434,10 +391,10 @@ func TestConfigs(t *testing.T) { // Aggregation Selector Tests { name: "WithAggregationSelector", - opts: []GenericOption{ + opts: []GRPCOption{ WithAggregationSelector(dropSelector), }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { + asserts: func(t *testing.T, c *Config) { // Function value comparisons are disallowed, test non-default // behavior of a AggregationSelector here to ensure our "catch // all" was set. @@ -450,12 +407,12 @@ func TestConfigs(t *testing.T) { // Proxy Tests { name: "Test With Proxy", - opts: []GenericOption{ + opts: []GRPCOption{ WithProxy(func(r *http.Request) (*url.URL, error) { return url.Parse("http://proxy.com") }), }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { + asserts: func(t *testing.T, c *Config) { assert.NotNil(t, c.Metrics.Proxy) proxyURL, err := c.Metrics.Proxy(&http.Request{}) assert.NoError(t, err) @@ -464,8 +421,8 @@ func TestConfigs(t *testing.T) { }, { name: "Test Without Proxy", - opts: []GenericOption{}, - asserts: func(t *testing.T, c *Config, grpcOption bool) { + opts: []GRPCOption{}, + asserts: func(t *testing.T, c *Config) { assert.Nil(t, c.Metrics.Proxy) }, }, @@ -481,13 +438,9 @@ func TestConfigs(t *testing.T) { } t.Cleanup(func() { DefaultEnvOptionsReader = origEOR }) - // Tests Generic options as HTTP Options - cfg := NewHTTPConfig(asHTTPOptions(tt.opts)...) - tt.asserts(t, &cfg, false) - // Tests Generic options as gRPC Options - cfg = NewGRPCConfig(asGRPCOptions(tt.opts)...) - tt.asserts(t, &cfg, true) + cfg := NewGRPCConfig(tt.opts...) + tt.asserts(t, &cfg) }) } } @@ -500,22 +453,6 @@ func deltaSelector(metric.InstrumentKind) metricdata.Temporality { return metricdata.DeltaTemporality } -func asHTTPOptions(opts []GenericOption) []HTTPOption { - converted := make([]HTTPOption, len(opts)) - for i, o := range opts { - converted[i] = NewHTTPOption(o.ApplyHTTPOption) - } - return converted -} - -func asGRPCOptions(opts []GenericOption) []GRPCOption { - converted := make([]GRPCOption, len(opts)) - for i, o := range opts { - converted[i] = NewGRPCOption(o.ApplyGRPCOption) - } - return converted -} - func TestCleanPath(t *testing.T) { type args struct { urlPath string diff --git a/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/otest/collector.go b/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/otest/collector.go index 6eea8d39a75..870ba5d685b 100644 --- a/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/otest/collector.go +++ b/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/otest/collector.go @@ -1,37 +1,16 @@ -// Code created by gotmpl. DO NOT MODIFY. -// source: internal/shared/otlp/otlpmetric/otest/collector.go.tmpl - // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 package otest // import "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/otest" import ( - "bytes" - "compress/gzip" - "context" - "crypto/ecdsa" - "crypto/elliptic" - "crypto/rand" - "crypto/tls" - "crypto/x509" - "crypto/x509/pkix" // nolint:depguard // This is for testing. - "encoding/pem" - "errors" - "fmt" - "io" - "math/big" + "context" // nolint:depguard // This is for testing. "net" - "net/http" - "net/url" "sync" - "time" "google.golang.org/grpc" "google.golang.org/grpc/metadata" - "google.golang.org/protobuf/proto" - "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc/internal/oconf" collpb "go.opentelemetry.io/proto/otlp/collector/metrics/v1" mpb "go.opentelemetry.io/proto/otlp/metrics/v1" ) @@ -41,6 +20,7 @@ type Collector interface { Collect() *Storage } +// ExportResult represents an export response. type ExportResult struct { Response *collpb.ExportMetricsServiceResponse Err error @@ -160,292 +140,3 @@ func (c *GRPCCollector) Export(ctx context.Context, req *collpb.ExportMetricsSer } return &collpb.ExportMetricsServiceResponse{}, nil } - -var emptyExportMetricsServiceResponse = func() []byte { - body := collpb.ExportMetricsServiceResponse{} - r, err := proto.Marshal(&body) - if err != nil { - panic(err) - } - return r -}() - -type HTTPResponseError struct { - Err error - Status int - Header http.Header -} - -func (e *HTTPResponseError) Error() string { - return fmt.Sprintf("%d: %s", e.Status, e.Err) -} - -func (e *HTTPResponseError) Unwrap() error { return e.Err } - -// HTTPCollector is an OTLP HTTP server that collects all requests it receives. -type HTTPCollector struct { - plainTextResponse bool - - headersMu sync.Mutex - headers http.Header - storage *Storage - - resultCh <-chan ExportResult - listener net.Listener - srv *http.Server -} - -// NewHTTPCollector returns a *HTTPCollector that is listening at the provided -// endpoint. -// -// If endpoint is an empty string, the returned collector will be listening on -// the localhost interface at an OS chosen port, not use TLS, and listen at the -// default OTLP metric endpoint path ("/v1/metrics"). If the endpoint contains -// a prefix of "https" the server will generate weak self-signed TLS -// certificates and use them to server data. If the endpoint contains a path, -// that path will be used instead of the default OTLP metric endpoint path. -// -// If errCh is not nil, the collector will respond to HTTP requests with errors -// sent on that channel. This means that if errCh is not nil Export calls will -// block until an error is received. -func NewHTTPCollector(endpoint string, resultCh <-chan ExportResult, opts ...func(*HTTPCollector)) (*HTTPCollector, error) { - u, err := url.Parse(endpoint) - if err != nil { - return nil, err - } - if u.Host == "" { - u.Host = "localhost:0" - } - if u.Path == "" { - u.Path = oconf.DefaultMetricsPath - } - - c := &HTTPCollector{ - headers: http.Header{}, - storage: NewStorage(), - resultCh: resultCh, - } - for _, opt := range opts { - opt(c) - } - - c.listener, err = net.Listen("tcp", u.Host) - if err != nil { - return nil, err - } - - mux := http.NewServeMux() - mux.Handle(u.Path, http.HandlerFunc(c.handler)) - c.srv = &http.Server{ - Handler: mux, - ReadTimeout: 10 * time.Second, - WriteTimeout: 10 * time.Second, - } - if u.Scheme == "https" { - cert, err := weakCertificate() - if err != nil { - return nil, err - } - c.srv.TLSConfig = &tls.Config{ - Certificates: []tls.Certificate{cert}, - } - go func() { _ = c.srv.ServeTLS(c.listener, "", "") }() - } else { - go func() { _ = c.srv.Serve(c.listener) }() - } - return c, nil -} - -// WithHTTPCollectorRespondingPlainText makes the HTTPCollector return -// a plaintext, instead of protobuf, response. -func WithHTTPCollectorRespondingPlainText() func(*HTTPCollector) { - return func(s *HTTPCollector) { - s.plainTextResponse = true - } -} - -// Shutdown shuts down the HTTP server closing all open connections and -// listeners. -func (c *HTTPCollector) Shutdown(ctx context.Context) error { - return c.srv.Shutdown(ctx) -} - -// Addr returns the net.Addr c is listening at. -func (c *HTTPCollector) Addr() net.Addr { - return c.listener.Addr() -} - -// Collect returns the Storage holding all collected requests. -func (c *HTTPCollector) Collect() *Storage { - return c.storage -} - -// Headers returns the headers received for all requests. -func (c *HTTPCollector) Headers() map[string][]string { - // Makes a copy. - c.headersMu.Lock() - defer c.headersMu.Unlock() - return c.headers.Clone() -} - -func (c *HTTPCollector) handler(w http.ResponseWriter, r *http.Request) { - c.respond(w, c.record(r)) -} - -func (c *HTTPCollector) record(r *http.Request) ExportResult { - // Currently only supports protobuf. - if v := r.Header.Get("Content-Type"); v != "application/x-protobuf" { - err := fmt.Errorf("content-type not supported: %s", v) - return ExportResult{Err: err} - } - - body, err := c.readBody(r) - if err != nil { - return ExportResult{Err: err} - } - pbRequest := &collpb.ExportMetricsServiceRequest{} - err = proto.Unmarshal(body, pbRequest) - if err != nil { - return ExportResult{ - Err: &HTTPResponseError{ - Err: err, - Status: http.StatusInternalServerError, - }, - } - } - c.storage.Add(pbRequest) - - c.headersMu.Lock() - for k, vals := range r.Header { - for _, v := range vals { - c.headers.Add(k, v) - } - } - c.headersMu.Unlock() - - if c.resultCh != nil { - return <-c.resultCh - } - return ExportResult{Err: err} -} - -func (c *HTTPCollector) readBody(r *http.Request) (body []byte, err error) { - var reader io.ReadCloser - switch r.Header.Get("Content-Encoding") { - case "gzip": - reader, err = gzip.NewReader(r.Body) - if err != nil { - _ = reader.Close() - return nil, &HTTPResponseError{ - Err: err, - Status: http.StatusInternalServerError, - } - } - default: - reader = r.Body - } - - defer func() { - cErr := reader.Close() - if err == nil && cErr != nil { - err = &HTTPResponseError{ - Err: cErr, - Status: http.StatusInternalServerError, - } - } - }() - body, err = io.ReadAll(reader) - if err != nil { - err = &HTTPResponseError{ - Err: err, - Status: http.StatusInternalServerError, - } - } - return body, err -} - -func (c *HTTPCollector) respond(w http.ResponseWriter, resp ExportResult) { - if resp.Err != nil { - w.Header().Set("Content-Type", "text/plain; charset=utf-8") - w.Header().Set("X-Content-Type-Options", "nosniff") - var e *HTTPResponseError - if errors.As(resp.Err, &e) { - for k, vals := range e.Header { - for _, v := range vals { - w.Header().Add(k, v) - } - } - w.WriteHeader(e.Status) - fmt.Fprintln(w, e.Error()) - } else { - w.WriteHeader(http.StatusBadRequest) - fmt.Fprintln(w, resp.Err.Error()) - } - return - } - - if c.plainTextResponse { - w.Header().Set("Content-Type", "text/plain; charset=utf-8") - w.WriteHeader(http.StatusOK) - _, _ = w.Write([]byte("OK")) - return - } - - w.Header().Set("Content-Type", "application/x-protobuf") - w.WriteHeader(http.StatusOK) - if resp.Response == nil { - _, _ = w.Write(emptyExportMetricsServiceResponse) - } else { - r, err := proto.Marshal(resp.Response) - if err != nil { - panic(err) - } - _, _ = w.Write(r) - } -} - -// Based on https://golang.org/src/crypto/tls/generate_cert.go, -// simplified and weakened. -func weakCertificate() (tls.Certificate, error) { - priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - if err != nil { - return tls.Certificate{}, err - } - notBefore := time.Now() - notAfter := notBefore.Add(time.Hour) - max := new(big.Int).Lsh(big.NewInt(1), 128) - sn, err := rand.Int(rand.Reader, max) - if err != nil { - return tls.Certificate{}, err - } - tmpl := x509.Certificate{ - SerialNumber: sn, - Subject: pkix.Name{Organization: []string{"otel-go"}}, - NotBefore: notBefore, - NotAfter: notAfter, - KeyUsage: x509.KeyUsageDigitalSignature, - 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(rand.Reader, &tmpl, &tmpl, &priv.PublicKey, priv) - if err != nil { - return tls.Certificate{}, err - } - var certBuf bytes.Buffer - err = pem.Encode(&certBuf, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) - if err != nil { - return tls.Certificate{}, err - } - privBytes, err := x509.MarshalPKCS8PrivateKey(priv) - if err != nil { - return tls.Certificate{}, err - } - var privBuf bytes.Buffer - err = pem.Encode(&privBuf, &pem.Block{Type: "PRIVATE KEY", Bytes: privBytes}) - if err != nil { - return tls.Certificate{}, err - } - return tls.X509KeyPair(certBuf.Bytes(), privBuf.Bytes()) -} diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/client.go b/exporters/otlp/otlpmetric/otlpmetrichttp/client.go index eeb39339d45..cca7fb41cbe 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/client.go +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/client.go @@ -23,8 +23,8 @@ import ( "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp/internal" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp/internal/oconf" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp/internal/retry" - colmetricpb "go.opentelemetry.io/proto/otlp/collector/metrics/v1" - metricpb "go.opentelemetry.io/proto/otlp/metrics/v1" + colmetricpb "go.opentelemetry.io/proto/slim/otlp/collector/metrics/v1" + metricpb "go.opentelemetry.io/proto/slim/otlp/metrics/v1" ) type client struct { diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/client_test.go b/exporters/otlp/otlpmetric/otlpmetrichttp/client_test.go index 2838fd9d011..cd04a8a1c0b 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/client_test.go +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/client_test.go @@ -21,7 +21,7 @@ import ( "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp/internal/otest" "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/metric/metricdata" - mpb "go.opentelemetry.io/proto/otlp/metrics/v1" + mpb "go.opentelemetry.io/proto/slim/otlp/metrics/v1" ) type clientShim struct { diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/exporter.go b/exporters/otlp/otlpmetric/otlpmetrichttp/exporter.go index 442d8096103..f8af8c65e22 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/exporter.go +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/exporter.go @@ -13,7 +13,7 @@ import ( "go.opentelemetry.io/otel/internal/global" "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/metric/metricdata" - metricpb "go.opentelemetry.io/proto/otlp/metrics/v1" + metricpb "go.opentelemetry.io/proto/slim/otlp/metrics/v1" ) // Exporter is a OpenTelemetry metric Exporter using protobufs over HTTP. diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/go.mod b/exporters/otlp/otlpmetric/otlpmetrichttp/go.mod index 4d8b0199025..41f8fb1bbad 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/go.mod +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/go.mod @@ -11,8 +11,7 @@ require ( go.opentelemetry.io/otel v1.25.0 go.opentelemetry.io/otel/sdk v1.25.0 go.opentelemetry.io/otel/sdk/metric v1.25.0 - go.opentelemetry.io/proto/otlp v1.2.0 - google.golang.org/grpc v1.63.2 + go.opentelemetry.io/proto/slim/otlp v1.2.0 google.golang.org/protobuf v1.33.0 ) @@ -20,17 +19,13 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 // indirect - github.com/kr/text v0.2.0 // indirect + github.com/kr/pretty v0.3.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rogpeppe/go-internal v1.11.0 // indirect go.opentelemetry.io/otel/metric v1.25.0 // indirect go.opentelemetry.io/otel/trace v1.25.0 // indirect - golang.org/x/net v0.23.0 // indirect golang.org/x/sys v0.19.0 // indirect - golang.org/x/text v0.14.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda // indirect + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/go.sum b/exporters/otlp/otlpmetric/otlpmetrichttp/go.sum index ea18bbbebdb..3bf21ce4259 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/go.sum +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/go.sum @@ -10,34 +10,25 @@ 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/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 h1:/c3QmbOGMGTOumP2iT/rCwB7b0QDGLKzqOmktBjT+Is= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1/go.mod h1:5SN9VR2LTsRFsrEC6FHgRbTWrTHu6tqPeKxEQv15giM= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 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/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -go.opentelemetry.io/proto/otlp v1.2.0 h1:pVeZGk7nXDC9O2hncA6nHldxEjm6LByfA2aN8IOkz94= -go.opentelemetry.io/proto/otlp v1.2.0/go.mod h1:gGpR8txAl5M03pDhMC79G6SdqNV26naRm/KDsgaHD8A= -golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= -golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +go.opentelemetry.io/proto/slim/otlp v1.2.0 h1:90eMxPHyObsdi/dB1ZP8FP3s3txzxVXjArYqLxPuLZg= +go.opentelemetry.io/proto/slim/otlp v1.2.0/go.mod h1:DeSHUkdUaCemrUs/Nmnsdo8BtM+XmdTEVjYWYFiLQhU= golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY= -google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:VUhTRKeHn9wwcdrk73nvdC9gF178Tzhmt/qyaFcPLSo= -google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de h1:jFNzHPIeuzhdRwVhbZdiym9q0ory/xY3sA+v2wPg8I0= -google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:5iCWqnniDlqZHrd3neWVTOwvh/v6s3232omMecelax8= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda h1:LI5DOvAxUPMv/50agcLLoo+AdWc1irS9Rzz4vPuD1V4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= -google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= -google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/internal/gen.go b/exporters/otlp/otlpmetric/otlpmetrichttp/internal/gen.go index 1b379f10c5e..51890a033a1 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/internal/gen.go +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/internal/gen.go @@ -12,20 +12,15 @@ package internal // import "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/o //go:generate gotmpl --body=../../../../../internal/shared/otlp/envconfig/envconfig.go.tmpl "--data={}" --out=envconfig/envconfig.go //go:generate gotmpl --body=../../../../../internal/shared/otlp/envconfig/envconfig_test.go.tmpl "--data={}" --out=envconfig/envconfig_test.go -//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/oconf/envconfig.go.tmpl "--data={\"envconfigImportPath\": \"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp/internal/envconfig\"}" --out=oconf/envconfig.go -//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/oconf/envconfig_test.go.tmpl "--data={}" --out=oconf/envconfig_test.go -//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/oconf/options.go.tmpl "--data={\"retryImportPath\": \"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp/internal/retry\"}" --out=oconf/options.go -//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/oconf/options_test.go.tmpl "--data={\"envconfigImportPath\": \"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp/internal/envconfig\"}" --out=oconf/options_test.go //go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/oconf/optiontypes.go.tmpl "--data={}" --out=oconf/optiontypes.go //go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/oconf/tls.go.tmpl "--data={}" --out=oconf/tls.go -//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/otest/client.go.tmpl "--data={}" --out=otest/client.go -//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/otest/client_test.go.tmpl "--data={\"internalImportPath\": \"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp/internal\"}" --out=otest/client_test.go -//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/otest/collector.go.tmpl "--data={\"oconfImportPath\": \"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp/internal/oconf\"}" --out=otest/collector.go +//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/otest/client.go.tmpl "--data={\"protoImportPrefix\": \"go.opentelemetry.io/proto/slim\"}" --out=otest/client.go +//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/otest/client_test.go.tmpl "--data={\"protoImportPrefix\": \"go.opentelemetry.io/proto/slim\", \"internalImportPath\": \"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp/internal\"}" --out=otest/client_test.go -//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/transform/attribute.go.tmpl "--data={}" --out=transform/attribute.go -//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/transform/attribute_test.go.tmpl "--data={}" --out=transform/attribute_test.go -//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/transform/error.go.tmpl "--data={}" --out=transform/error.go +//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/transform/attribute.go.tmpl "--data={\"protoImportPrefix\": \"go.opentelemetry.io/proto/slim\"}" --out=transform/attribute.go +//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/transform/attribute_test.go.tmpl "--data={\"protoImportPrefix\": \"go.opentelemetry.io/proto/slim\"}" --out=transform/attribute_test.go +//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/transform/error.go.tmpl "--data={\"protoImportPrefix\": \"go.opentelemetry.io/proto/slim\"}" --out=transform/error.go //go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/transform/error_test.go.tmpl "--data={}" --out=transform/error_test.go -//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/transform/metricdata.go.tmpl "--data={}" --out=transform/metricdata.go -//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/transform/metricdata_test.go.tmpl "--data={}" --out=transform/metricdata_test.go +//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/transform/metricdata.go.tmpl "--data={\"protoImportPrefix\": \"go.opentelemetry.io/proto/slim\"}" --out=transform/metricdata.go +//go:generate gotmpl --body=../../../../../internal/shared/otlp/otlpmetric/transform/metricdata_test.go.tmpl "--data={\"protoImportPrefix\": \"go.opentelemetry.io/proto/slim\"}" --out=transform/metricdata_test.go diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/internal/oconf/envconfig.go b/exporters/otlp/otlpmetric/otlpmetrichttp/internal/oconf/envconfig.go index 89b134a39fb..ba641347081 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/internal/oconf/envconfig.go +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/internal/oconf/envconfig.go @@ -1,6 +1,3 @@ -// Code created by gotmpl. DO NOT MODIFY. -// source: internal/shared/otlp/otlpmetric/oconf/envconfig.go.tmpl - // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 @@ -28,15 +25,6 @@ var DefaultEnvOptionsReader = envconfig.EnvOptionsReader{ Namespace: "OTEL_EXPORTER_OTLP", } -// ApplyGRPCEnvConfigs applies the env configurations for gRPC. -func ApplyGRPCEnvConfigs(cfg Config) Config { - opts := getOptionsFromEnv() - for _, opt := range opts { - cfg = opt.ApplyGRPCOption(cfg) - } - return cfg -} - // ApplyHTTPEnvConfigs applies the env configurations for HTTP. func ApplyHTTPEnvConfigs(cfg Config) Config { opts := getOptionsFromEnv() @@ -46,25 +34,25 @@ func ApplyHTTPEnvConfigs(cfg Config) Config { return cfg } -func getOptionsFromEnv() []GenericOption { - opts := []GenericOption{} +func getOptionsFromEnv() []HTTPOption { + opts := []HTTPOption{} tlsConf := &tls.Config{} DefaultEnvOptionsReader.Apply( envconfig.WithURL("ENDPOINT", func(u *url.URL) { opts = append(opts, withEndpointScheme(u)) - opts = append(opts, newSplitOption(func(cfg Config) Config { + opts = append(opts, NewHTTPOption(func(cfg Config) Config { cfg.Metrics.Endpoint = u.Host // For OTLP/HTTP endpoint URLs without a per-signal // configuration, the passed endpoint is used as a base URL // and the signals are sent to these paths relative to that. cfg.Metrics.URLPath = path.Join(u.Path, DefaultMetricsPath) return cfg - }, withEndpointForGRPC(u))) + })) }), envconfig.WithURL("METRICS_ENDPOINT", func(u *url.URL) { opts = append(opts, withEndpointScheme(u)) - opts = append(opts, newSplitOption(func(cfg Config) Config { + opts = append(opts, NewHTTPOption(func(cfg Config) Config { cfg.Metrics.Endpoint = u.Host // For endpoint URLs for OTLP/HTTP per-signal variables, the // URL MUST be used as-is without any modification. The only @@ -76,7 +64,7 @@ func getOptionsFromEnv() []GenericOption { } cfg.Metrics.URLPath = path return cfg - }, withEndpointForGRPC(u))) + })) }), envconfig.WithCertPool("CERTIFICATE", func(p *x509.CertPool) { tlsConf.RootCAs = p }), envconfig.WithCertPool("METRICS_CERTIFICATE", func(p *x509.CertPool) { tlsConf.RootCAs = p }), @@ -98,15 +86,6 @@ func getOptionsFromEnv() []GenericOption { return opts } -func withEndpointForGRPC(u *url.URL) func(cfg Config) Config { - return func(cfg Config) Config { - // For OTLP/gRPC endpoints, this is the target to which the - // exporter is going to send telemetry. - cfg.Metrics.Endpoint = path.Join(u.Host, u.Path) - return cfg - } -} - // WithEnvCompression retrieves the specified config and passes it to ConfigFn as a Compression. func WithEnvCompression(n string, fn func(Compression)) func(e *envconfig.EnvOptionsReader) { return func(e *envconfig.EnvOptionsReader) { @@ -121,7 +100,7 @@ func WithEnvCompression(n string, fn func(Compression)) func(e *envconfig.EnvOpt } } -func withEndpointScheme(u *url.URL) GenericOption { +func withEndpointScheme(u *url.URL) HTTPOption { switch strings.ToLower(u.Scheme) { case "http", "unix": return WithInsecure() @@ -131,7 +110,7 @@ func withEndpointScheme(u *url.URL) GenericOption { } // revive:disable-next-line:flag-parameter -func withInsecure(b bool) GenericOption { +func withInsecure(b bool) HTTPOption { if b { return WithInsecure() } diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/internal/oconf/envconfig_test.go b/exporters/otlp/otlpmetric/otlpmetrichttp/internal/oconf/envconfig_test.go index 5c621d7b47e..ba99959db56 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/internal/oconf/envconfig_test.go +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/internal/oconf/envconfig_test.go @@ -1,6 +1,3 @@ -// Code created by gotmpl. DO NOT MODIFY. -// source: internal/shared/otlp/otlpmetric/oconf/envconfig_test.go.tmpl - // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 @@ -77,7 +74,7 @@ func TestWithEnvTemporalityPreference(t *testing.T) { return origReader(key) } cfg := Config{} - cfg = ApplyGRPCEnvConfigs(cfg) + cfg = ApplyHTTPEnvConfigs(cfg) if tt.want == nil { // There is no function set, the SDK's default is used. @@ -147,7 +144,7 @@ func TestWithEnvAggPreference(t *testing.T) { return origReader(key) } cfg := Config{} - cfg = ApplyGRPCEnvConfigs(cfg) + cfg = ApplyHTTPEnvConfigs(cfg) if tt.want == nil { // There is no function set, the SDK's default is used. diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/internal/oconf/options.go b/exporters/otlp/otlpmetric/otlpmetrichttp/internal/oconf/options.go index 9bbf0941f94..1763b381988 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/internal/oconf/options.go +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/internal/oconf/options.go @@ -1,6 +1,3 @@ -// Code created by gotmpl. DO NOT MODIFY. -// source: internal/shared/otlp/otlpmetric/oconf/options.go.tmpl - // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 @@ -15,12 +12,6 @@ import ( "strings" "time" - "google.golang.org/grpc" - "google.golang.org/grpc/backoff" - "google.golang.org/grpc/credentials" - "google.golang.org/grpc/credentials/insecure" - "google.golang.org/grpc/encoding/gzip" - "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp/internal/retry" "go.opentelemetry.io/otel/internal/global" "go.opentelemetry.io/otel/sdk/metric" @@ -47,6 +38,7 @@ type ( // This type is compatible with `http.Transport.Proxy` and can be used to set a custom proxy function to the OTLP HTTP client. HTTPTransportProxyFunc func(*http.Request) (*url.URL, error) + // SignalConfig represents signal specific configuration. SignalConfig struct { Endpoint string Insecure bool @@ -56,26 +48,17 @@ type ( Timeout time.Duration URLPath string - // gRPC configurations - GRPCCredentials credentials.TransportCredentials - TemporalitySelector metric.TemporalitySelector AggregationSelector metric.AggregationSelector Proxy HTTPTransportProxyFunc } + // Config represents exporter configuration. Config struct { - // Signal specific configurations Metrics SignalConfig RetryConfig retry.Config - - // gRPC configurations - ReconnectionPeriod time.Duration - ServiceConfig string - DialOptions []grpc.DialOption - GRPCConn *grpc.ClientConn } ) @@ -116,126 +99,14 @@ func cleanPath(urlPath string, defaultPath string) string { return tmp } -// NewGRPCConfig returns a new Config with all settings applied from opts and -// any unset setting using the default gRPC config values. -func NewGRPCConfig(opts ...GRPCOption) Config { - cfg := Config{ - Metrics: SignalConfig{ - Endpoint: fmt.Sprintf("%s:%d", DefaultCollectorHost, DefaultCollectorGRPCPort), - URLPath: DefaultMetricsPath, - Compression: NoCompression, - Timeout: DefaultTimeout, - - TemporalitySelector: metric.DefaultTemporalitySelector, - AggregationSelector: metric.DefaultAggregationSelector, - }, - RetryConfig: retry.DefaultConfig, - } - cfg = ApplyGRPCEnvConfigs(cfg) - for _, opt := range opts { - cfg = opt.ApplyGRPCOption(cfg) - } - - if cfg.ServiceConfig != "" { - cfg.DialOptions = append(cfg.DialOptions, grpc.WithDefaultServiceConfig(cfg.ServiceConfig)) - } - // Priroritize GRPCCredentials over Insecure (passing both is an error). - if cfg.Metrics.GRPCCredentials != nil { - cfg.DialOptions = append(cfg.DialOptions, grpc.WithTransportCredentials(cfg.Metrics.GRPCCredentials)) - } else if cfg.Metrics.Insecure { - cfg.DialOptions = append(cfg.DialOptions, grpc.WithTransportCredentials(insecure.NewCredentials())) - } else { - // Default to using the host's root CA. - creds := credentials.NewTLS(nil) - cfg.Metrics.GRPCCredentials = creds - cfg.DialOptions = append(cfg.DialOptions, grpc.WithTransportCredentials(creds)) - } - if cfg.Metrics.Compression == GzipCompression { - cfg.DialOptions = append(cfg.DialOptions, grpc.WithDefaultCallOptions(grpc.UseCompressor(gzip.Name))) - } - if cfg.ReconnectionPeriod != 0 { - p := grpc.ConnectParams{ - Backoff: backoff.DefaultConfig, - MinConnectTimeout: cfg.ReconnectionPeriod, - } - cfg.DialOptions = append(cfg.DialOptions, grpc.WithConnectParams(p)) - } +// HTTPOption applies an option to the HTTP driver. +type HTTPOption interface { + ApplyHTTPOption(Config) Config - return cfg -} - -type ( - // GenericOption applies an option to the HTTP or gRPC driver. - GenericOption interface { - ApplyHTTPOption(Config) Config - ApplyGRPCOption(Config) Config - - // A private method to prevent users implementing the - // interface and so future additions to it will not - // violate compatibility. - private() - } - - // HTTPOption applies an option to the HTTP driver. - HTTPOption interface { - ApplyHTTPOption(Config) Config - - // A private method to prevent users implementing the - // interface and so future additions to it will not - // violate compatibility. - private() - } - - // GRPCOption applies an option to the gRPC driver. - GRPCOption interface { - ApplyGRPCOption(Config) Config - - // A private method to prevent users implementing the - // interface and so future additions to it will not - // violate compatibility. - private() - } -) - -// genericOption is an option that applies the same logic -// for both gRPC and HTTP. -type genericOption struct { - fn func(Config) Config -} - -func (g *genericOption) ApplyGRPCOption(cfg Config) Config { - return g.fn(cfg) -} - -func (g *genericOption) ApplyHTTPOption(cfg Config) Config { - return g.fn(cfg) -} - -func (genericOption) private() {} - -func newGenericOption(fn func(cfg Config) Config) GenericOption { - return &genericOption{fn: fn} -} - -// splitOption is an option that applies different logics -// for gRPC and HTTP. -type splitOption struct { - httpFn func(Config) Config - grpcFn func(Config) Config -} - -func (g *splitOption) ApplyGRPCOption(cfg Config) Config { - return g.grpcFn(cfg) -} - -func (g *splitOption) ApplyHTTPOption(cfg Config) Config { - return g.httpFn(cfg) -} - -func (splitOption) private() {} - -func newSplitOption(httpFn func(cfg Config) Config, grpcFn func(cfg Config) Config) GenericOption { - return &splitOption{httpFn: httpFn, grpcFn: grpcFn} + // A private method to prevent users implementing the + // interface and so future additions to it will not + // violate compatibility. + private() } // httpOption is an option that is only applied to the HTTP driver. @@ -253,32 +124,17 @@ func NewHTTPOption(fn func(cfg Config) Config) HTTPOption { return &httpOption{fn: fn} } -// grpcOption is an option that is only applied to the gRPC driver. -type grpcOption struct { - fn func(Config) Config -} - -func (h *grpcOption) ApplyGRPCOption(cfg Config) Config { - return h.fn(cfg) -} - -func (grpcOption) private() {} - -func NewGRPCOption(fn func(cfg Config) Config) GRPCOption { - return &grpcOption{fn: fn} -} - // Generic Options -func WithEndpoint(endpoint string) GenericOption { - return newGenericOption(func(cfg Config) Config { +func WithEndpoint(endpoint string) HTTPOption { + return NewHTTPOption(func(cfg Config) Config { cfg.Metrics.Endpoint = endpoint return cfg }) } -func WithEndpointURL(v string) GenericOption { - return newGenericOption(func(cfg Config) Config { +func WithEndpointURL(v string) HTTPOption { + return NewHTTPOption(func(cfg Config) Config { u, err := url.Parse(v) if err != nil { global.Error(err, "otlpmetric: parse endpoint url", "url", v) @@ -295,81 +151,78 @@ func WithEndpointURL(v string) GenericOption { }) } -func WithCompression(compression Compression) GenericOption { - return newGenericOption(func(cfg Config) Config { +func WithCompression(compression Compression) HTTPOption { + return NewHTTPOption(func(cfg Config) Config { cfg.Metrics.Compression = compression return cfg }) } -func WithURLPath(urlPath string) GenericOption { - return newGenericOption(func(cfg Config) Config { +func WithURLPath(urlPath string) HTTPOption { + return NewHTTPOption(func(cfg Config) Config { cfg.Metrics.URLPath = urlPath return cfg }) } -func WithRetry(rc retry.Config) GenericOption { - return newGenericOption(func(cfg Config) Config { +func WithRetry(rc retry.Config) HTTPOption { + return NewHTTPOption(func(cfg Config) Config { cfg.RetryConfig = rc return cfg }) } -func WithTLSClientConfig(tlsCfg *tls.Config) GenericOption { - return newSplitOption(func(cfg Config) Config { +func WithTLSClientConfig(tlsCfg *tls.Config) HTTPOption { + return NewHTTPOption(func(cfg Config) Config { cfg.Metrics.TLSCfg = tlsCfg.Clone() return cfg - }, func(cfg Config) Config { - cfg.Metrics.GRPCCredentials = credentials.NewTLS(tlsCfg) - return cfg }) } -func WithInsecure() GenericOption { - return newGenericOption(func(cfg Config) Config { +func WithInsecure() HTTPOption { + return NewHTTPOption(func(cfg Config) Config { cfg.Metrics.Insecure = true return cfg }) } -func WithSecure() GenericOption { - return newGenericOption(func(cfg Config) Config { +func WithSecure() HTTPOption { + return NewHTTPOption(func(cfg Config) Config { cfg.Metrics.Insecure = false return cfg }) } -func WithHeaders(headers map[string]string) GenericOption { - return newGenericOption(func(cfg Config) Config { +func WithHeaders(headers map[string]string) HTTPOption { + return NewHTTPOption(func(cfg Config) Config { cfg.Metrics.Headers = headers return cfg }) } -func WithTimeout(duration time.Duration) GenericOption { - return newGenericOption(func(cfg Config) Config { +func WithTimeout(duration time.Duration) HTTPOption { + return NewHTTPOption(func(cfg Config) Config { cfg.Metrics.Timeout = duration return cfg }) } -func WithTemporalitySelector(selector metric.TemporalitySelector) GenericOption { - return newGenericOption(func(cfg Config) Config { +func WithTemporalitySelector(selector metric.TemporalitySelector) HTTPOption { + return NewHTTPOption(func(cfg Config) Config { cfg.Metrics.TemporalitySelector = selector return cfg }) } -func WithAggregationSelector(selector metric.AggregationSelector) GenericOption { - return newGenericOption(func(cfg Config) Config { +func WithAggregationSelector(selector metric.AggregationSelector) HTTPOption { + return NewHTTPOption(func(cfg Config) Config { cfg.Metrics.AggregationSelector = selector return cfg }) } -func WithProxy(pf HTTPTransportProxyFunc) GenericOption { - return newGenericOption(func(cfg Config) Config { +func WithProxy(pf HTTPTransportProxyFunc) HTTPOption { + return NewHTTPOption(func(cfg Config) Config { cfg.Metrics.Proxy = pf return cfg }) diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/internal/oconf/options_test.go b/exporters/otlp/otlpmetric/otlpmetrichttp/internal/oconf/options_test.go index 3401f8ec524..8f6cd3e81ba 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/internal/oconf/options_test.go +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/internal/oconf/options_test.go @@ -1,6 +1,3 @@ -// Code created by gotmpl. DO NOT MODIFY. -// source: internal/shared/otlp/otlpmetric/oconf/options_test.go.tmpl - // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 @@ -64,19 +61,15 @@ func TestConfigs(t *testing.T) { tests := []struct { name string - opts []GenericOption + opts []HTTPOption env env fileReader fileReader - asserts func(t *testing.T, c *Config, grpcOption bool) + asserts func(t *testing.T, c *Config) }{ { name: "Test default configs", - asserts: func(t *testing.T, c *Config, grpcOption bool) { - if grpcOption { - assert.Equal(t, "localhost:4317", c.Metrics.Endpoint) - } else { - assert.Equal(t, "localhost:4318", c.Metrics.Endpoint) - } + asserts: func(t *testing.T, c *Config) { + assert.Equal(t, "localhost:4318", c.Metrics.Endpoint) assert.Equal(t, NoCompression, c.Metrics.Compression) assert.Equal(t, map[string]string(nil), c.Metrics.Headers) assert.Equal(t, 10*time.Second, c.Metrics.Timeout) @@ -86,19 +79,19 @@ func TestConfigs(t *testing.T) { // Endpoint Tests { name: "Test With Endpoint", - opts: []GenericOption{ + opts: []HTTPOption{ WithEndpoint("someendpoint"), }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { + asserts: func(t *testing.T, c *Config) { assert.Equal(t, "someendpoint", c.Metrics.Endpoint) }, }, { name: "Test With Endpoint URL", - opts: []GenericOption{ + opts: []HTTPOption{ WithEndpointURL("http://someendpoint/somepath"), }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { + asserts: func(t *testing.T, c *Config) { assert.Equal(t, "someendpoint", c.Metrics.Endpoint) assert.Equal(t, "/somepath", c.Metrics.URLPath) assert.Equal(t, true, c.Metrics.Insecure) @@ -106,10 +99,10 @@ func TestConfigs(t *testing.T) { }, { name: "Test With Secure Endpoint URL", - opts: []GenericOption{ + opts: []HTTPOption{ WithEndpointURL("https://someendpoint/somepath"), }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { + asserts: func(t *testing.T, c *Config) { assert.Equal(t, "someendpoint", c.Metrics.Endpoint) assert.Equal(t, "/somepath", c.Metrics.URLPath) assert.Equal(t, false, c.Metrics.Insecure) @@ -117,15 +110,11 @@ func TestConfigs(t *testing.T) { }, { name: "Test With Invalid Endpoint URL", - opts: []GenericOption{ + opts: []HTTPOption{ WithEndpointURL("%invalid"), }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { - if grpcOption { - assert.Equal(t, "localhost:4317", c.Metrics.Endpoint) - } else { - assert.Equal(t, "localhost:4318", c.Metrics.Endpoint) - } + asserts: func(t *testing.T, c *Config) { + assert.Equal(t, "localhost:4318", c.Metrics.Endpoint) assert.Equal(t, "/v1/metrics", c.Metrics.URLPath) }, }, @@ -134,14 +123,10 @@ func TestConfigs(t *testing.T) { env: map[string]string{ "OTEL_EXPORTER_OTLP_ENDPOINT": "https://env.endpoint/prefix", }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { + asserts: func(t *testing.T, c *Config) { assert.False(t, c.Metrics.Insecure) - if grpcOption { - assert.Equal(t, "env.endpoint/prefix", c.Metrics.Endpoint) - } else { - assert.Equal(t, "env.endpoint", c.Metrics.Endpoint) - assert.Equal(t, "/prefix/v1/metrics", c.Metrics.URLPath) - } + assert.Equal(t, "env.endpoint", c.Metrics.Endpoint) + assert.Equal(t, "/prefix/v1/metrics", c.Metrics.URLPath) }, }, { @@ -150,23 +135,21 @@ func TestConfigs(t *testing.T) { "OTEL_EXPORTER_OTLP_ENDPOINT": "https://overrode.by.signal.specific/env/var", "OTEL_EXPORTER_OTLP_METRICS_ENDPOINT": "http://env.metrics.endpoint", }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { + asserts: func(t *testing.T, c *Config) { assert.True(t, c.Metrics.Insecure) assert.Equal(t, "env.metrics.endpoint", c.Metrics.Endpoint) - if !grpcOption { - assert.Equal(t, "/", c.Metrics.URLPath) - } + assert.Equal(t, "/", c.Metrics.URLPath) }, }, { name: "Test Mixed Environment and With Endpoint", - opts: []GenericOption{ + opts: []HTTPOption{ WithEndpoint("metrics_endpoint"), }, env: map[string]string{ "OTEL_EXPORTER_OTLP_ENDPOINT": "env_endpoint", }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { + asserts: func(t *testing.T, c *Config) { assert.Equal(t, "metrics_endpoint", c.Metrics.Endpoint) }, }, @@ -175,7 +158,7 @@ func TestConfigs(t *testing.T) { env: map[string]string{ "OTEL_EXPORTER_OTLP_ENDPOINT": "http://env_endpoint", }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { + asserts: func(t *testing.T, c *Config) { assert.Equal(t, "env_endpoint", c.Metrics.Endpoint) assert.Equal(t, true, c.Metrics.Insecure) }, @@ -185,7 +168,7 @@ func TestConfigs(t *testing.T) { env: map[string]string{ "OTEL_EXPORTER_OTLP_ENDPOINT": " http://env_endpoint ", }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { + asserts: func(t *testing.T, c *Config) { assert.Equal(t, "env_endpoint", c.Metrics.Endpoint) assert.Equal(t, true, c.Metrics.Insecure) }, @@ -195,7 +178,7 @@ func TestConfigs(t *testing.T) { env: map[string]string{ "OTEL_EXPORTER_OTLP_ENDPOINT": "https://env_endpoint", }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { + asserts: func(t *testing.T, c *Config) { assert.Equal(t, "env_endpoint", c.Metrics.Endpoint) assert.Equal(t, false, c.Metrics.Insecure) }, @@ -206,7 +189,7 @@ func TestConfigs(t *testing.T) { "OTEL_EXPORTER_OTLP_ENDPOINT": "HTTPS://overrode_by_signal_specific", "OTEL_EXPORTER_OTLP_METRICS_ENDPOINT": "HtTp://env_metrics_endpoint", }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { + asserts: func(t *testing.T, c *Config) { assert.Equal(t, "env_metrics_endpoint", c.Metrics.Endpoint) assert.Equal(t, true, c.Metrics.Insecure) }, @@ -215,27 +198,18 @@ func TestConfigs(t *testing.T) { // Certificate tests { name: "Test Default Certificate", - asserts: func(t *testing.T, c *Config, grpcOption bool) { - if grpcOption { - assert.NotNil(t, c.Metrics.GRPCCredentials) - } else { - assert.Nil(t, c.Metrics.TLSCfg) - } + asserts: func(t *testing.T, c *Config) { + assert.Nil(t, c.Metrics.TLSCfg) }, }, { name: "Test With Certificate", - opts: []GenericOption{ + opts: []HTTPOption{ WithTLSClientConfig(tlsCert), }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { - if grpcOption { - // TODO: make sure gRPC's credentials actually works - assert.NotNil(t, c.Metrics.GRPCCredentials) - } else { - // nolint:staticcheck // ignoring tlsCert.RootCAs.Subjects is deprecated ERR because cert does not come from SystemCertPool. - assert.Equal(t, tlsCert.RootCAs.Subjects(), c.Metrics.TLSCfg.RootCAs.Subjects()) - } + asserts: func(t *testing.T, c *Config) { + // nolint:staticcheck // ignoring tlsCert.RootCAs.Subjects is deprecated ERR because cert does not come from SystemCertPool. + assert.Equal(t, tlsCert.RootCAs.Subjects(), c.Metrics.TLSCfg.RootCAs.Subjects()) }, }, { @@ -246,13 +220,9 @@ func TestConfigs(t *testing.T) { fileReader: fileReader{ "cert_path": []byte(WeakCertificate), }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { - if grpcOption { - assert.NotNil(t, c.Metrics.GRPCCredentials) - } else { - // nolint:staticcheck // ignoring tlsCert.RootCAs.Subjects is deprecated ERR because cert does not come from SystemCertPool. - assert.Equal(t, tlsCert.RootCAs.Subjects(), c.Metrics.TLSCfg.RootCAs.Subjects()) - } + asserts: func(t *testing.T, c *Config) { + // nolint:staticcheck // ignoring tlsCert.RootCAs.Subjects is deprecated ERR because cert does not come from SystemCertPool. + assert.Equal(t, tlsCert.RootCAs.Subjects(), c.Metrics.TLSCfg.RootCAs.Subjects()) }, }, { @@ -265,48 +235,40 @@ func TestConfigs(t *testing.T) { "cert_path": []byte(WeakCertificate), "invalid_cert": []byte("invalid certificate file."), }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { - if grpcOption { - assert.NotNil(t, c.Metrics.GRPCCredentials) - } else { - // nolint:staticcheck // ignoring tlsCert.RootCAs.Subjects is deprecated ERR because cert does not come from SystemCertPool. - assert.Equal(t, tlsCert.RootCAs.Subjects(), c.Metrics.TLSCfg.RootCAs.Subjects()) - } + asserts: func(t *testing.T, c *Config) { + // nolint:staticcheck // ignoring tlsCert.RootCAs.Subjects is deprecated ERR because cert does not come from SystemCertPool. + assert.Equal(t, tlsCert.RootCAs.Subjects(), c.Metrics.TLSCfg.RootCAs.Subjects()) }, }, { name: "Test Mixed Environment and With Certificate", - opts: []GenericOption{}, + opts: []HTTPOption{}, env: map[string]string{ "OTEL_EXPORTER_OTLP_CERTIFICATE": "cert_path", }, fileReader: fileReader{ "cert_path": []byte(WeakCertificate), }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { - if grpcOption { - assert.NotNil(t, c.Metrics.GRPCCredentials) - } else { - // nolint:staticcheck // ignoring tlsCert.RootCAs.Subjects is deprecated ERR because cert does not come from SystemCertPool. - assert.Equal(t, 1, len(c.Metrics.TLSCfg.RootCAs.Subjects())) - } + asserts: func(t *testing.T, c *Config) { + // nolint:staticcheck // ignoring tlsCert.RootCAs.Subjects is deprecated ERR because cert does not come from SystemCertPool. + assert.Equal(t, 1, len(c.Metrics.TLSCfg.RootCAs.Subjects())) }, }, // Headers tests { name: "Test With Headers", - opts: []GenericOption{ + opts: []HTTPOption{ WithHeaders(map[string]string{"h1": "v1"}), }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { + asserts: func(t *testing.T, c *Config) { assert.Equal(t, map[string]string{"h1": "v1"}, c.Metrics.Headers) }, }, { name: "Test Environment Headers", env: map[string]string{"OTEL_EXPORTER_OTLP_HEADERS": "h1=v1,h2=v2"}, - asserts: func(t *testing.T, c *Config, grpcOption bool) { + asserts: func(t *testing.T, c *Config) { assert.Equal(t, map[string]string{"h1": "v1", "h2": "v2"}, c.Metrics.Headers) }, }, @@ -316,17 +278,17 @@ func TestConfigs(t *testing.T) { "OTEL_EXPORTER_OTLP_HEADERS": "overrode_by_signal_specific", "OTEL_EXPORTER_OTLP_METRICS_HEADERS": "h1=v1,h2=v2", }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { + asserts: func(t *testing.T, c *Config) { assert.Equal(t, map[string]string{"h1": "v1", "h2": "v2"}, c.Metrics.Headers) }, }, { name: "Test Mixed Environment and With Headers", env: map[string]string{"OTEL_EXPORTER_OTLP_HEADERS": "h1=v1,h2=v2"}, - opts: []GenericOption{ + opts: []HTTPOption{ WithHeaders(map[string]string{"m1": "mv1"}), }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { + asserts: func(t *testing.T, c *Config) { assert.Equal(t, map[string]string{"m1": "mv1"}, c.Metrics.Headers) }, }, @@ -334,10 +296,10 @@ func TestConfigs(t *testing.T) { // Compression Tests { name: "Test With Compression", - opts: []GenericOption{ + opts: []HTTPOption{ WithCompression(GzipCompression), }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { + asserts: func(t *testing.T, c *Config) { assert.Equal(t, GzipCompression, c.Metrics.Compression) }, }, @@ -346,7 +308,7 @@ func TestConfigs(t *testing.T) { env: map[string]string{ "OTEL_EXPORTER_OTLP_COMPRESSION": "gzip", }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { + asserts: func(t *testing.T, c *Config) { assert.Equal(t, GzipCompression, c.Metrics.Compression) }, }, @@ -355,19 +317,19 @@ func TestConfigs(t *testing.T) { env: map[string]string{ "OTEL_EXPORTER_OTLP_METRICS_COMPRESSION": "gzip", }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { + asserts: func(t *testing.T, c *Config) { assert.Equal(t, GzipCompression, c.Metrics.Compression) }, }, { name: "Test Mixed Environment and With Compression", - opts: []GenericOption{ + opts: []HTTPOption{ WithCompression(NoCompression), }, env: map[string]string{ "OTEL_EXPORTER_OTLP_METRICS_COMPRESSION": "gzip", }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { + asserts: func(t *testing.T, c *Config) { assert.Equal(t, NoCompression, c.Metrics.Compression) }, }, @@ -375,10 +337,10 @@ func TestConfigs(t *testing.T) { // Timeout Tests { name: "Test With Timeout", - opts: []GenericOption{ + opts: []HTTPOption{ WithTimeout(time.Duration(5 * time.Second)), }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { + asserts: func(t *testing.T, c *Config) { assert.Equal(t, 5*time.Second, c.Metrics.Timeout) }, }, @@ -387,7 +349,7 @@ func TestConfigs(t *testing.T) { env: map[string]string{ "OTEL_EXPORTER_OTLP_TIMEOUT": "15000", }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { + asserts: func(t *testing.T, c *Config) { assert.Equal(t, c.Metrics.Timeout, 15*time.Second) }, }, @@ -397,7 +359,7 @@ func TestConfigs(t *testing.T) { "OTEL_EXPORTER_OTLP_TIMEOUT": "15000", "OTEL_EXPORTER_OTLP_METRICS_TIMEOUT": "28000", }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { + asserts: func(t *testing.T, c *Config) { assert.Equal(t, c.Metrics.Timeout, 28*time.Second) }, }, @@ -407,10 +369,10 @@ func TestConfigs(t *testing.T) { "OTEL_EXPORTER_OTLP_TIMEOUT": "15000", "OTEL_EXPORTER_OTLP_METRICS_TIMEOUT": "28000", }, - opts: []GenericOption{ + opts: []HTTPOption{ WithTimeout(5 * time.Second), }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { + asserts: func(t *testing.T, c *Config) { assert.Equal(t, c.Metrics.Timeout, 5*time.Second) }, }, @@ -418,10 +380,10 @@ func TestConfigs(t *testing.T) { // Temporality Selector Tests { name: "WithTemporalitySelector", - opts: []GenericOption{ + opts: []HTTPOption{ WithTemporalitySelector(deltaSelector), }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { + asserts: func(t *testing.T, c *Config) { // Function value comparisons are disallowed, test non-default // behavior of a TemporalitySelector here to ensure our "catch // all" was set. @@ -434,10 +396,10 @@ func TestConfigs(t *testing.T) { // Aggregation Selector Tests { name: "WithAggregationSelector", - opts: []GenericOption{ + opts: []HTTPOption{ WithAggregationSelector(dropSelector), }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { + asserts: func(t *testing.T, c *Config) { // Function value comparisons are disallowed, test non-default // behavior of a AggregationSelector here to ensure our "catch // all" was set. @@ -450,12 +412,12 @@ func TestConfigs(t *testing.T) { // Proxy Tests { name: "Test With Proxy", - opts: []GenericOption{ + opts: []HTTPOption{ WithProxy(func(r *http.Request) (*url.URL, error) { return url.Parse("http://proxy.com") }), }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { + asserts: func(t *testing.T, c *Config) { assert.NotNil(t, c.Metrics.Proxy) proxyURL, err := c.Metrics.Proxy(&http.Request{}) assert.NoError(t, err) @@ -464,8 +426,8 @@ func TestConfigs(t *testing.T) { }, { name: "Test Without Proxy", - opts: []GenericOption{}, - asserts: func(t *testing.T, c *Config, grpcOption bool) { + opts: []HTTPOption{}, + asserts: func(t *testing.T, c *Config) { assert.Nil(t, c.Metrics.Proxy) }, }, @@ -482,12 +444,8 @@ func TestConfigs(t *testing.T) { t.Cleanup(func() { DefaultEnvOptionsReader = origEOR }) // Tests Generic options as HTTP Options - cfg := NewHTTPConfig(asHTTPOptions(tt.opts)...) - tt.asserts(t, &cfg, false) - - // Tests Generic options as gRPC Options - cfg = NewGRPCConfig(asGRPCOptions(tt.opts)...) - tt.asserts(t, &cfg, true) + cfg := NewHTTPConfig(tt.opts...) + tt.asserts(t, &cfg) }) } } @@ -500,22 +458,6 @@ func deltaSelector(metric.InstrumentKind) metricdata.Temporality { return metricdata.DeltaTemporality } -func asHTTPOptions(opts []GenericOption) []HTTPOption { - converted := make([]HTTPOption, len(opts)) - for i, o := range opts { - converted[i] = NewHTTPOption(o.ApplyHTTPOption) - } - return converted -} - -func asGRPCOptions(opts []GenericOption) []GRPCOption { - converted := make([]GRPCOption, len(opts)) - for i, o := range opts { - converted[i] = NewGRPCOption(o.ApplyGRPCOption) - } - return converted -} - func TestCleanPath(t *testing.T) { type args struct { urlPath string diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/internal/otest/client.go b/exporters/otlp/otlpmetric/otlpmetrichttp/internal/otest/client.go index 9f6e6122fcc..dcc2bbb58e0 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/internal/otest/client.go +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/internal/otest/client.go @@ -19,10 +19,10 @@ import ( "go.opentelemetry.io/otel" semconv "go.opentelemetry.io/otel/semconv/v1.24.0" - collpb "go.opentelemetry.io/proto/otlp/collector/metrics/v1" - cpb "go.opentelemetry.io/proto/otlp/common/v1" - mpb "go.opentelemetry.io/proto/otlp/metrics/v1" - rpb "go.opentelemetry.io/proto/otlp/resource/v1" + collpb "go.opentelemetry.io/proto/slim/otlp/collector/metrics/v1" + cpb "go.opentelemetry.io/proto/slim/otlp/common/v1" + mpb "go.opentelemetry.io/proto/slim/otlp/metrics/v1" + rpb "go.opentelemetry.io/proto/slim/otlp/resource/v1" ) var ( diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/internal/otest/client_test.go b/exporters/otlp/otlpmetric/otlpmetrichttp/internal/otest/client_test.go index ccf3a578d70..12f71d7cbb9 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/internal/otest/client_test.go +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/internal/otest/client_test.go @@ -14,8 +14,8 @@ import ( "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp/internal" "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/metric/metricdata" - cpb "go.opentelemetry.io/proto/otlp/collector/metrics/v1" - mpb "go.opentelemetry.io/proto/otlp/metrics/v1" + cpb "go.opentelemetry.io/proto/slim/otlp/collector/metrics/v1" + mpb "go.opentelemetry.io/proto/slim/otlp/metrics/v1" ) type client struct { diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/internal/otest/collector.go b/exporters/otlp/otlpmetric/otlpmetrichttp/internal/otest/collector.go index 178dcde6c36..71489ff2d0f 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/internal/otest/collector.go +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/internal/otest/collector.go @@ -1,6 +1,3 @@ -// Code created by gotmpl. DO NOT MODIFY. -// source: internal/shared/otlp/otlpmetric/otest/collector.go.tmpl - // Copyright The OpenTelemetry Authors // SPDX-License-Identifier: Apache-2.0 @@ -27,13 +24,11 @@ import ( "sync" "time" - "google.golang.org/grpc" - "google.golang.org/grpc/metadata" "google.golang.org/protobuf/proto" "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp/internal/oconf" - collpb "go.opentelemetry.io/proto/otlp/collector/metrics/v1" - mpb "go.opentelemetry.io/proto/otlp/metrics/v1" + collpb "go.opentelemetry.io/proto/slim/otlp/collector/metrics/v1" + mpb "go.opentelemetry.io/proto/slim/otlp/metrics/v1" ) // Collector is the collection target a Client sends metric uploads to. @@ -41,6 +36,7 @@ type Collector interface { Collect() *Storage } +// ExportResult represents an export response. type ExportResult struct { Response *collpb.ExportMetricsServiceResponse Err error @@ -74,93 +70,6 @@ func (s *Storage) Dump() []*mpb.ResourceMetrics { return data } -// GRPCCollector is an OTLP gRPC server that collects all requests it receives. -type GRPCCollector struct { - collpb.UnimplementedMetricsServiceServer - - headersMu sync.Mutex - headers metadata.MD - storage *Storage - - resultCh <-chan ExportResult - listener net.Listener - srv *grpc.Server -} - -// NewGRPCCollector returns a *GRPCCollector that is listening at the provided -// endpoint. -// -// If endpoint is an empty string, the returned collector will be listening on -// the localhost interface at an OS chosen port. -// -// If errCh is not nil, the collector will respond to Export calls with errors -// sent on that channel. This means that if errCh is not nil Export calls will -// block until an error is received. -func NewGRPCCollector(endpoint string, resultCh <-chan ExportResult) (*GRPCCollector, error) { - if endpoint == "" { - endpoint = "localhost:0" - } - - c := &GRPCCollector{ - storage: NewStorage(), - resultCh: resultCh, - } - - var err error - c.listener, err = net.Listen("tcp", endpoint) - if err != nil { - return nil, err - } - - c.srv = grpc.NewServer() - collpb.RegisterMetricsServiceServer(c.srv, c) - go func() { _ = c.srv.Serve(c.listener) }() - - return c, nil -} - -// Shutdown shuts down the gRPC server closing all open connections and -// listeners immediately. -func (c *GRPCCollector) Shutdown() { c.srv.Stop() } - -// Addr returns the net.Addr c is listening at. -func (c *GRPCCollector) Addr() net.Addr { - return c.listener.Addr() -} - -// Collect returns the Storage holding all collected requests. -func (c *GRPCCollector) Collect() *Storage { - return c.storage -} - -// Headers returns the headers received for all requests. -func (c *GRPCCollector) Headers() map[string][]string { - // Makes a copy. - c.headersMu.Lock() - defer c.headersMu.Unlock() - return metadata.Join(c.headers) -} - -// Export handles the export req. -func (c *GRPCCollector) Export(ctx context.Context, req *collpb.ExportMetricsServiceRequest) (*collpb.ExportMetricsServiceResponse, error) { - c.storage.Add(req) - - if h, ok := metadata.FromIncomingContext(ctx); ok { - c.headersMu.Lock() - c.headers = metadata.Join(c.headers, h) - c.headersMu.Unlock() - } - - if c.resultCh != nil { - r := <-c.resultCh - if r.Response == nil { - return &collpb.ExportMetricsServiceResponse{}, r.Err - } - return r.Response, r.Err - } - return &collpb.ExportMetricsServiceResponse{}, nil -} - var emptyExportMetricsServiceResponse = func() []byte { body := collpb.ExportMetricsServiceResponse{} r, err := proto.Marshal(&body) @@ -170,6 +79,7 @@ var emptyExportMetricsServiceResponse = func() []byte { return r }() +// HTTPResponseError is used to mock a HTTP response error. type HTTPResponseError struct { Err error Status int diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/internal/transform/attribute.go b/exporters/otlp/otlpmetric/otlpmetrichttp/internal/transform/attribute.go index d607da78eaf..8cfe42191e3 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/internal/transform/attribute.go +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/internal/transform/attribute.go @@ -8,7 +8,7 @@ package transform // import "go.opentelemetry.io/otel/exporters/otlp/otlpmetric/ import ( "go.opentelemetry.io/otel/attribute" - cpb "go.opentelemetry.io/proto/otlp/common/v1" + cpb "go.opentelemetry.io/proto/slim/otlp/common/v1" ) // AttrIter transforms an attribute iterator into OTLP key-values. diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/internal/transform/attribute_test.go b/exporters/otlp/otlpmetric/otlpmetrichttp/internal/transform/attribute_test.go index 0815ba988d2..bd2b6c305a7 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/internal/transform/attribute_test.go +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/internal/transform/attribute_test.go @@ -12,7 +12,7 @@ import ( "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel/attribute" - cpb "go.opentelemetry.io/proto/otlp/common/v1" + cpb "go.opentelemetry.io/proto/slim/otlp/common/v1" ) var ( diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/internal/transform/error.go b/exporters/otlp/otlpmetric/otlpmetrichttp/internal/transform/error.go index 60d0d1f72ae..f9700704ad8 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/internal/transform/error.go +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/internal/transform/error.go @@ -11,7 +11,7 @@ import ( "fmt" "strings" - mpb "go.opentelemetry.io/proto/otlp/metrics/v1" + mpb "go.opentelemetry.io/proto/slim/otlp/metrics/v1" ) var ( diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/internal/transform/metricdata.go b/exporters/otlp/otlpmetric/otlpmetrichttp/internal/transform/metricdata.go index 04c2ce75704..34fca043c26 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/internal/transform/metricdata.go +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/internal/transform/metricdata.go @@ -13,9 +13,9 @@ import ( "time" "go.opentelemetry.io/otel/sdk/metric/metricdata" - cpb "go.opentelemetry.io/proto/otlp/common/v1" - mpb "go.opentelemetry.io/proto/otlp/metrics/v1" - rpb "go.opentelemetry.io/proto/otlp/resource/v1" + cpb "go.opentelemetry.io/proto/slim/otlp/common/v1" + mpb "go.opentelemetry.io/proto/slim/otlp/metrics/v1" + rpb "go.opentelemetry.io/proto/slim/otlp/resource/v1" ) // ResourceMetrics returns an OTLP ResourceMetrics generated from rm. If rm diff --git a/exporters/otlp/otlpmetric/otlpmetrichttp/internal/transform/metricdata_test.go b/exporters/otlp/otlpmetric/otlpmetrichttp/internal/transform/metricdata_test.go index b0bc71e9edb..00b20c0ed67 100644 --- a/exporters/otlp/otlpmetric/otlpmetrichttp/internal/transform/metricdata_test.go +++ b/exporters/otlp/otlpmetric/otlpmetrichttp/internal/transform/metricdata_test.go @@ -18,9 +18,9 @@ import ( "go.opentelemetry.io/otel/sdk/metric/metricdata" "go.opentelemetry.io/otel/sdk/resource" semconv "go.opentelemetry.io/otel/semconv/v1.24.0" - cpb "go.opentelemetry.io/proto/otlp/common/v1" - mpb "go.opentelemetry.io/proto/otlp/metrics/v1" - rpb "go.opentelemetry.io/proto/otlp/resource/v1" + cpb "go.opentelemetry.io/proto/slim/otlp/common/v1" + mpb "go.opentelemetry.io/proto/slim/otlp/metrics/v1" + rpb "go.opentelemetry.io/proto/slim/otlp/resource/v1" ) type unknownAggT struct { diff --git a/internal/shared/otlp/otlpmetric/oconf/envconfig.go.tmpl b/internal/shared/otlp/otlpmetric/oconf/envconfig.go.tmpl deleted file mode 100644 index 5334c9b3911..00000000000 --- a/internal/shared/otlp/otlpmetric/oconf/envconfig.go.tmpl +++ /dev/null @@ -1,210 +0,0 @@ -// Code created by gotmpl. DO NOT MODIFY. -// source: internal/shared/otlp/otlpmetric/oconf/envconfig.go.tmpl - -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package oconf - -import ( - "crypto/tls" - "crypto/x509" - "net/url" - "os" - "path" - "strings" - "time" - - "{{ .envconfigImportPath }}" - "go.opentelemetry.io/otel/internal/global" - "go.opentelemetry.io/otel/sdk/metric" - "go.opentelemetry.io/otel/sdk/metric/metricdata" -) - -// DefaultEnvOptionsReader is the default environments reader. -var DefaultEnvOptionsReader = envconfig.EnvOptionsReader{ - GetEnv: os.Getenv, - ReadFile: os.ReadFile, - Namespace: "OTEL_EXPORTER_OTLP", -} - -// ApplyGRPCEnvConfigs applies the env configurations for gRPC. -func ApplyGRPCEnvConfigs(cfg Config) Config { - opts := getOptionsFromEnv() - for _, opt := range opts { - cfg = opt.ApplyGRPCOption(cfg) - } - return cfg -} - -// ApplyHTTPEnvConfigs applies the env configurations for HTTP. -func ApplyHTTPEnvConfigs(cfg Config) Config { - opts := getOptionsFromEnv() - for _, opt := range opts { - cfg = opt.ApplyHTTPOption(cfg) - } - return cfg -} - -func getOptionsFromEnv() []GenericOption { - opts := []GenericOption{} - - tlsConf := &tls.Config{} - DefaultEnvOptionsReader.Apply( - envconfig.WithURL("ENDPOINT", func(u *url.URL) { - opts = append(opts, withEndpointScheme(u)) - opts = append(opts, newSplitOption(func(cfg Config) Config { - cfg.Metrics.Endpoint = u.Host - // For OTLP/HTTP endpoint URLs without a per-signal - // configuration, the passed endpoint is used as a base URL - // and the signals are sent to these paths relative to that. - cfg.Metrics.URLPath = path.Join(u.Path, DefaultMetricsPath) - return cfg - }, withEndpointForGRPC(u))) - }), - envconfig.WithURL("METRICS_ENDPOINT", func(u *url.URL) { - opts = append(opts, withEndpointScheme(u)) - opts = append(opts, newSplitOption(func(cfg Config) Config { - cfg.Metrics.Endpoint = u.Host - // For endpoint URLs for OTLP/HTTP per-signal variables, the - // URL MUST be used as-is without any modification. The only - // exception is that if an URL contains no path part, the root - // path / MUST be used. - path := u.Path - if path == "" { - path = "/" - } - cfg.Metrics.URLPath = path - return cfg - }, withEndpointForGRPC(u))) - }), - envconfig.WithCertPool("CERTIFICATE", func(p *x509.CertPool) { tlsConf.RootCAs = p }), - envconfig.WithCertPool("METRICS_CERTIFICATE", func(p *x509.CertPool) { tlsConf.RootCAs = p }), - envconfig.WithClientCert("CLIENT_CERTIFICATE", "CLIENT_KEY", func(c tls.Certificate) { tlsConf.Certificates = []tls.Certificate{c} }), - envconfig.WithClientCert("METRICS_CLIENT_CERTIFICATE", "METRICS_CLIENT_KEY", func(c tls.Certificate) { tlsConf.Certificates = []tls.Certificate{c} }), - envconfig.WithBool("INSECURE", func(b bool) { opts = append(opts, withInsecure(b)) }), - envconfig.WithBool("METRICS_INSECURE", func(b bool) { opts = append(opts, withInsecure(b)) }), - withTLSConfig(tlsConf, func(c *tls.Config) { opts = append(opts, WithTLSClientConfig(c)) }), - envconfig.WithHeaders("HEADERS", func(h map[string]string) { opts = append(opts, WithHeaders(h)) }), - envconfig.WithHeaders("METRICS_HEADERS", func(h map[string]string) { opts = append(opts, WithHeaders(h)) }), - WithEnvCompression("COMPRESSION", func(c Compression) { opts = append(opts, WithCompression(c)) }), - WithEnvCompression("METRICS_COMPRESSION", func(c Compression) { opts = append(opts, WithCompression(c)) }), - envconfig.WithDuration("TIMEOUT", func(d time.Duration) { opts = append(opts, WithTimeout(d)) }), - envconfig.WithDuration("METRICS_TIMEOUT", func(d time.Duration) { opts = append(opts, WithTimeout(d)) }), - withEnvTemporalityPreference("METRICS_TEMPORALITY_PREFERENCE", func(t metric.TemporalitySelector) { opts = append(opts, WithTemporalitySelector(t)) }), - withEnvAggPreference("METRICS_DEFAULT_HISTOGRAM_AGGREGATION", func(a metric.AggregationSelector) { opts = append(opts, WithAggregationSelector(a)) }), - ) - - return opts -} - -func withEndpointForGRPC(u *url.URL) func(cfg Config) Config { - return func(cfg Config) Config { - // For OTLP/gRPC endpoints, this is the target to which the - // exporter is going to send telemetry. - cfg.Metrics.Endpoint = path.Join(u.Host, u.Path) - return cfg - } -} - -// WithEnvCompression retrieves the specified config and passes it to ConfigFn as a Compression. -func WithEnvCompression(n string, fn func(Compression)) func(e *envconfig.EnvOptionsReader) { - return func(e *envconfig.EnvOptionsReader) { - if v, ok := e.GetEnvValue(n); ok { - cp := NoCompression - if v == "gzip" { - cp = GzipCompression - } - - fn(cp) - } - } -} - -func withEndpointScheme(u *url.URL) GenericOption { - switch strings.ToLower(u.Scheme) { - case "http", "unix": - return WithInsecure() - default: - return WithSecure() - } -} - -// revive:disable-next-line:flag-parameter -func withInsecure(b bool) GenericOption { - if b { - return WithInsecure() - } - return WithSecure() -} - -func withTLSConfig(c *tls.Config, fn func(*tls.Config)) func(e *envconfig.EnvOptionsReader) { - return func(e *envconfig.EnvOptionsReader) { - if c.RootCAs != nil || len(c.Certificates) > 0 { - fn(c) - } - } -} - -func withEnvTemporalityPreference(n string, fn func(metric.TemporalitySelector)) func(e *envconfig.EnvOptionsReader) { - return func(e *envconfig.EnvOptionsReader) { - if s, ok := e.GetEnvValue(n); ok { - switch strings.ToLower(s) { - case "cumulative": - fn(cumulativeTemporality) - case "delta": - fn(deltaTemporality) - case "lowmemory": - fn(lowMemory) - default: - global.Warn("OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE is set to an invalid value, ignoring.", "value", s) - } - } - } -} - -func cumulativeTemporality(metric.InstrumentKind) metricdata.Temporality { - return metricdata.CumulativeTemporality -} - -func deltaTemporality(ik metric.InstrumentKind) metricdata.Temporality { - switch ik { - case metric.InstrumentKindCounter, metric.InstrumentKindHistogram, metric.InstrumentKindObservableCounter: - return metricdata.DeltaTemporality - default: - return metricdata.CumulativeTemporality - } -} - -func lowMemory(ik metric.InstrumentKind) metricdata.Temporality { - switch ik { - case metric.InstrumentKindCounter, metric.InstrumentKindHistogram: - return metricdata.DeltaTemporality - default: - return metricdata.CumulativeTemporality - } -} - -func withEnvAggPreference(n string, fn func(metric.AggregationSelector)) func(e *envconfig.EnvOptionsReader) { - return func(e *envconfig.EnvOptionsReader) { - if s, ok := e.GetEnvValue(n); ok { - switch strings.ToLower(s) { - case "explicit_bucket_histogram": - fn(metric.DefaultAggregationSelector) - case "base2_exponential_bucket_histogram": - fn(func(kind metric.InstrumentKind) metric.Aggregation { - if kind == metric.InstrumentKindHistogram { - return metric.AggregationBase2ExponentialHistogram{ - MaxSize: 160, - MaxScale: 20, - NoMinMax: false, - } - } - return metric.DefaultAggregationSelector(kind) - }) - default: - global.Warn("OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION is set to an invalid value, ignoring.", "value", s) - } - } - } -} diff --git a/internal/shared/otlp/otlpmetric/oconf/envconfig_test.go.tmpl b/internal/shared/otlp/otlpmetric/oconf/envconfig_test.go.tmpl deleted file mode 100644 index 5c621d7b47e..00000000000 --- a/internal/shared/otlp/otlpmetric/oconf/envconfig_test.go.tmpl +++ /dev/null @@ -1,165 +0,0 @@ -// Code created by gotmpl. DO NOT MODIFY. -// source: internal/shared/otlp/otlpmetric/oconf/envconfig_test.go.tmpl - -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package oconf - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "go.opentelemetry.io/otel/sdk/metric" - "go.opentelemetry.io/otel/sdk/metric/metricdata" -) - -func TestWithEnvTemporalityPreference(t *testing.T) { - origReader := DefaultEnvOptionsReader.GetEnv - tests := []struct { - name string - envValue string - want map[metric.InstrumentKind]metricdata.Temporality - }{ - { - name: "default do not set the selector", - envValue: "", - }, - { - name: "non-normative do not set the selector", - envValue: "non-normative", - }, - { - name: "cumulative", - envValue: "cumulative", - want: map[metric.InstrumentKind]metricdata.Temporality{ - metric.InstrumentKindCounter: metricdata.CumulativeTemporality, - metric.InstrumentKindHistogram: metricdata.CumulativeTemporality, - metric.InstrumentKindUpDownCounter: metricdata.CumulativeTemporality, - metric.InstrumentKindObservableCounter: metricdata.CumulativeTemporality, - metric.InstrumentKindObservableUpDownCounter: metricdata.CumulativeTemporality, - metric.InstrumentKindObservableGauge: metricdata.CumulativeTemporality, - }, - }, - { - name: "delta", - envValue: "delta", - want: map[metric.InstrumentKind]metricdata.Temporality{ - metric.InstrumentKindCounter: metricdata.DeltaTemporality, - metric.InstrumentKindHistogram: metricdata.DeltaTemporality, - metric.InstrumentKindUpDownCounter: metricdata.CumulativeTemporality, - metric.InstrumentKindObservableCounter: metricdata.DeltaTemporality, - metric.InstrumentKindObservableUpDownCounter: metricdata.CumulativeTemporality, - metric.InstrumentKindObservableGauge: metricdata.CumulativeTemporality, - }, - }, - { - name: "lowmemory", - envValue: "lowmemory", - want: map[metric.InstrumentKind]metricdata.Temporality{ - metric.InstrumentKindCounter: metricdata.DeltaTemporality, - metric.InstrumentKindHistogram: metricdata.DeltaTemporality, - metric.InstrumentKindUpDownCounter: metricdata.CumulativeTemporality, - metric.InstrumentKindObservableCounter: metricdata.CumulativeTemporality, - metric.InstrumentKindObservableUpDownCounter: metricdata.CumulativeTemporality, - metric.InstrumentKindObservableGauge: metricdata.CumulativeTemporality, - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - DefaultEnvOptionsReader.GetEnv = func(key string) string { - if key == "OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE" { - return tt.envValue - } - return origReader(key) - } - cfg := Config{} - cfg = ApplyGRPCEnvConfigs(cfg) - - if tt.want == nil { - // There is no function set, the SDK's default is used. - assert.Nil(t, cfg.Metrics.TemporalitySelector) - return - } - - require.NotNil(t, cfg.Metrics.TemporalitySelector) - for ik, want := range tt.want { - assert.Equal(t, want, cfg.Metrics.TemporalitySelector(ik)) - } - }) - } - DefaultEnvOptionsReader.GetEnv = origReader -} - -func TestWithEnvAggPreference(t *testing.T) { - origReader := DefaultEnvOptionsReader.GetEnv - tests := []struct { - name string - envValue string - want map[metric.InstrumentKind]metric.Aggregation - }{ - { - name: "default do not set the selector", - envValue: "", - }, - { - name: "non-normative do not set the selector", - envValue: "non-normative", - }, - { - name: "explicit_bucket_histogram", - envValue: "explicit_bucket_histogram", - want: map[metric.InstrumentKind]metric.Aggregation{ - metric.InstrumentKindCounter: metric.DefaultAggregationSelector(metric.InstrumentKindCounter), - metric.InstrumentKindHistogram: metric.DefaultAggregationSelector(metric.InstrumentKindHistogram), - metric.InstrumentKindUpDownCounter: metric.DefaultAggregationSelector(metric.InstrumentKindUpDownCounter), - metric.InstrumentKindObservableCounter: metric.DefaultAggregationSelector(metric.InstrumentKindObservableCounter), - metric.InstrumentKindObservableUpDownCounter: metric.DefaultAggregationSelector(metric.InstrumentKindObservableUpDownCounter), - metric.InstrumentKindObservableGauge: metric.DefaultAggregationSelector(metric.InstrumentKindObservableGauge), - }, - }, - { - name: "base2_exponential_bucket_histogram", - envValue: "base2_exponential_bucket_histogram", - want: map[metric.InstrumentKind]metric.Aggregation{ - metric.InstrumentKindCounter: metric.DefaultAggregationSelector(metric.InstrumentKindCounter), - metric.InstrumentKindHistogram: metric.AggregationBase2ExponentialHistogram{ - MaxSize: 160, - MaxScale: 20, - NoMinMax: false, - }, - metric.InstrumentKindUpDownCounter: metric.DefaultAggregationSelector(metric.InstrumentKindUpDownCounter), - metric.InstrumentKindObservableCounter: metric.DefaultAggregationSelector(metric.InstrumentKindObservableCounter), - metric.InstrumentKindObservableUpDownCounter: metric.DefaultAggregationSelector(metric.InstrumentKindObservableUpDownCounter), - metric.InstrumentKindObservableGauge: metric.DefaultAggregationSelector(metric.InstrumentKindObservableGauge), - }, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - DefaultEnvOptionsReader.GetEnv = func(key string) string { - if key == "OTEL_EXPORTER_OTLP_METRICS_DEFAULT_HISTOGRAM_AGGREGATION" { - return tt.envValue - } - return origReader(key) - } - cfg := Config{} - cfg = ApplyGRPCEnvConfigs(cfg) - - if tt.want == nil { - // There is no function set, the SDK's default is used. - assert.Nil(t, cfg.Metrics.AggregationSelector) - return - } - - require.NotNil(t, cfg.Metrics.AggregationSelector) - for ik, want := range tt.want { - assert.Equal(t, want, cfg.Metrics.AggregationSelector(ik)) - } - }) - } - DefaultEnvOptionsReader.GetEnv = origReader -} diff --git a/internal/shared/otlp/otlpmetric/oconf/options.go.tmpl b/internal/shared/otlp/otlpmetric/oconf/options.go.tmpl deleted file mode 100644 index 071a144fdf5..00000000000 --- a/internal/shared/otlp/otlpmetric/oconf/options.go.tmpl +++ /dev/null @@ -1,376 +0,0 @@ -// Code created by gotmpl. DO NOT MODIFY. -// source: internal/shared/otlp/otlpmetric/oconf/options.go.tmpl - -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package oconf - -import ( - "crypto/tls" - "fmt" - "net/http" - "net/url" - "path" - "strings" - "time" - - "google.golang.org/grpc" - "google.golang.org/grpc/backoff" - "google.golang.org/grpc/credentials" - "google.golang.org/grpc/credentials/insecure" - "google.golang.org/grpc/encoding/gzip" - - "{{ .retryImportPath }}" - "go.opentelemetry.io/otel/internal/global" - "go.opentelemetry.io/otel/sdk/metric" -) - -const ( - // DefaultMaxAttempts describes how many times the driver - // should retry the sending of the payload in case of a - // retryable error. - DefaultMaxAttempts int = 5 - // DefaultMetricsPath is a default URL path for endpoint that - // receives metrics. - DefaultMetricsPath string = "/v1/metrics" - // DefaultBackoff is a default base backoff time used in the - // exponential backoff strategy. - DefaultBackoff time.Duration = 300 * time.Millisecond - // DefaultTimeout is a default max waiting time for the backend to process - // each span or metrics batch. - DefaultTimeout time.Duration = 10 * time.Second -) - -type ( - // HTTPTransportProxyFunc is a function that resolves which URL to use as proxy for a given request. - // This type is compatible with `http.Transport.Proxy` and can be used to set a custom proxy function to the OTLP HTTP client. - HTTPTransportProxyFunc func(*http.Request) (*url.URL, error) - - SignalConfig struct { - Endpoint string - Insecure bool - TLSCfg *tls.Config - Headers map[string]string - Compression Compression - Timeout time.Duration - URLPath string - - // gRPC configurations - GRPCCredentials credentials.TransportCredentials - - TemporalitySelector metric.TemporalitySelector - AggregationSelector metric.AggregationSelector - - Proxy HTTPTransportProxyFunc - } - - Config struct { - // Signal specific configurations - Metrics SignalConfig - - RetryConfig retry.Config - - // gRPC configurations - ReconnectionPeriod time.Duration - ServiceConfig string - DialOptions []grpc.DialOption - GRPCConn *grpc.ClientConn - } -) - -// NewHTTPConfig returns a new Config with all settings applied from opts and -// any unset setting using the default HTTP config values. -func NewHTTPConfig(opts ...HTTPOption) Config { - cfg := Config{ - Metrics: SignalConfig{ - Endpoint: fmt.Sprintf("%s:%d", DefaultCollectorHost, DefaultCollectorHTTPPort), - URLPath: DefaultMetricsPath, - Compression: NoCompression, - Timeout: DefaultTimeout, - - TemporalitySelector: metric.DefaultTemporalitySelector, - AggregationSelector: metric.DefaultAggregationSelector, - }, - RetryConfig: retry.DefaultConfig, - } - cfg = ApplyHTTPEnvConfigs(cfg) - for _, opt := range opts { - cfg = opt.ApplyHTTPOption(cfg) - } - cfg.Metrics.URLPath = cleanPath(cfg.Metrics.URLPath, DefaultMetricsPath) - return cfg -} - -// cleanPath returns a path with all spaces trimmed and all redundancies -// removed. If urlPath is empty or cleaning it results in an empty string, -// defaultPath is returned instead. -func cleanPath(urlPath string, defaultPath string) string { - tmp := path.Clean(strings.TrimSpace(urlPath)) - if tmp == "." { - return defaultPath - } - if !path.IsAbs(tmp) { - tmp = fmt.Sprintf("/%s", tmp) - } - return tmp -} - -// NewGRPCConfig returns a new Config with all settings applied from opts and -// any unset setting using the default gRPC config values. -func NewGRPCConfig(opts ...GRPCOption) Config { - cfg := Config{ - Metrics: SignalConfig{ - Endpoint: fmt.Sprintf("%s:%d", DefaultCollectorHost, DefaultCollectorGRPCPort), - URLPath: DefaultMetricsPath, - Compression: NoCompression, - Timeout: DefaultTimeout, - - TemporalitySelector: metric.DefaultTemporalitySelector, - AggregationSelector: metric.DefaultAggregationSelector, - }, - RetryConfig: retry.DefaultConfig, - } - cfg = ApplyGRPCEnvConfigs(cfg) - for _, opt := range opts { - cfg = opt.ApplyGRPCOption(cfg) - } - - if cfg.ServiceConfig != "" { - cfg.DialOptions = append(cfg.DialOptions, grpc.WithDefaultServiceConfig(cfg.ServiceConfig)) - } - // Priroritize GRPCCredentials over Insecure (passing both is an error). - if cfg.Metrics.GRPCCredentials != nil { - cfg.DialOptions = append(cfg.DialOptions, grpc.WithTransportCredentials(cfg.Metrics.GRPCCredentials)) - } else if cfg.Metrics.Insecure { - cfg.DialOptions = append(cfg.DialOptions, grpc.WithTransportCredentials(insecure.NewCredentials())) - } else { - // Default to using the host's root CA. - creds := credentials.NewTLS(nil) - cfg.Metrics.GRPCCredentials = creds - cfg.DialOptions = append(cfg.DialOptions, grpc.WithTransportCredentials(creds)) - } - if cfg.Metrics.Compression == GzipCompression { - cfg.DialOptions = append(cfg.DialOptions, grpc.WithDefaultCallOptions(grpc.UseCompressor(gzip.Name))) - } - if cfg.ReconnectionPeriod != 0 { - p := grpc.ConnectParams{ - Backoff: backoff.DefaultConfig, - MinConnectTimeout: cfg.ReconnectionPeriod, - } - cfg.DialOptions = append(cfg.DialOptions, grpc.WithConnectParams(p)) - } - - return cfg -} - -type ( - // GenericOption applies an option to the HTTP or gRPC driver. - GenericOption interface { - ApplyHTTPOption(Config) Config - ApplyGRPCOption(Config) Config - - // A private method to prevent users implementing the - // interface and so future additions to it will not - // violate compatibility. - private() - } - - // HTTPOption applies an option to the HTTP driver. - HTTPOption interface { - ApplyHTTPOption(Config) Config - - // A private method to prevent users implementing the - // interface and so future additions to it will not - // violate compatibility. - private() - } - - // GRPCOption applies an option to the gRPC driver. - GRPCOption interface { - ApplyGRPCOption(Config) Config - - // A private method to prevent users implementing the - // interface and so future additions to it will not - // violate compatibility. - private() - } -) - -// genericOption is an option that applies the same logic -// for both gRPC and HTTP. -type genericOption struct { - fn func(Config) Config -} - -func (g *genericOption) ApplyGRPCOption(cfg Config) Config { - return g.fn(cfg) -} - -func (g *genericOption) ApplyHTTPOption(cfg Config) Config { - return g.fn(cfg) -} - -func (genericOption) private() {} - -func newGenericOption(fn func(cfg Config) Config) GenericOption { - return &genericOption{fn: fn} -} - -// splitOption is an option that applies different logics -// for gRPC and HTTP. -type splitOption struct { - httpFn func(Config) Config - grpcFn func(Config) Config -} - -func (g *splitOption) ApplyGRPCOption(cfg Config) Config { - return g.grpcFn(cfg) -} - -func (g *splitOption) ApplyHTTPOption(cfg Config) Config { - return g.httpFn(cfg) -} - -func (splitOption) private() {} - -func newSplitOption(httpFn func(cfg Config) Config, grpcFn func(cfg Config) Config) GenericOption { - return &splitOption{httpFn: httpFn, grpcFn: grpcFn} -} - -// httpOption is an option that is only applied to the HTTP driver. -type httpOption struct { - fn func(Config) Config -} - -func (h *httpOption) ApplyHTTPOption(cfg Config) Config { - return h.fn(cfg) -} - -func (httpOption) private() {} - -func NewHTTPOption(fn func(cfg Config) Config) HTTPOption { - return &httpOption{fn: fn} -} - -// grpcOption is an option that is only applied to the gRPC driver. -type grpcOption struct { - fn func(Config) Config -} - -func (h *grpcOption) ApplyGRPCOption(cfg Config) Config { - return h.fn(cfg) -} - -func (grpcOption) private() {} - -func NewGRPCOption(fn func(cfg Config) Config) GRPCOption { - return &grpcOption{fn: fn} -} - -// Generic Options - -func WithEndpoint(endpoint string) GenericOption { - return newGenericOption(func(cfg Config) Config { - cfg.Metrics.Endpoint = endpoint - return cfg - }) -} - -func WithEndpointURL(v string) GenericOption { - return newGenericOption(func(cfg Config) Config { - u, err := url.Parse(v) - if err != nil { - global.Error(err, "otlpmetric: parse endpoint url", "url", v) - return cfg - } - - cfg.Metrics.Endpoint = u.Host - cfg.Metrics.URLPath = u.Path - if u.Scheme != "https" { - cfg.Metrics.Insecure = true - } - - return cfg - }) -} - -func WithCompression(compression Compression) GenericOption { - return newGenericOption(func(cfg Config) Config { - cfg.Metrics.Compression = compression - return cfg - }) -} - -func WithURLPath(urlPath string) GenericOption { - return newGenericOption(func(cfg Config) Config { - cfg.Metrics.URLPath = urlPath - return cfg - }) -} - -func WithRetry(rc retry.Config) GenericOption { - return newGenericOption(func(cfg Config) Config { - cfg.RetryConfig = rc - return cfg - }) -} - -func WithTLSClientConfig(tlsCfg *tls.Config) GenericOption { - return newSplitOption(func(cfg Config) Config { - cfg.Metrics.TLSCfg = tlsCfg.Clone() - return cfg - }, func(cfg Config) Config { - cfg.Metrics.GRPCCredentials = credentials.NewTLS(tlsCfg) - return cfg - }) -} - -func WithInsecure() GenericOption { - return newGenericOption(func(cfg Config) Config { - cfg.Metrics.Insecure = true - return cfg - }) -} - -func WithSecure() GenericOption { - return newGenericOption(func(cfg Config) Config { - cfg.Metrics.Insecure = false - return cfg - }) -} - -func WithHeaders(headers map[string]string) GenericOption { - return newGenericOption(func(cfg Config) Config { - cfg.Metrics.Headers = headers - return cfg - }) -} - -func WithTimeout(duration time.Duration) GenericOption { - return newGenericOption(func(cfg Config) Config { - cfg.Metrics.Timeout = duration - return cfg - }) -} - -func WithTemporalitySelector(selector metric.TemporalitySelector) GenericOption { - return newGenericOption(func(cfg Config) Config { - cfg.Metrics.TemporalitySelector = selector - return cfg - }) -} - -func WithAggregationSelector(selector metric.AggregationSelector) GenericOption { - return newGenericOption(func(cfg Config) Config { - cfg.Metrics.AggregationSelector = selector - return cfg - }) -} - -func WithProxy(pf HTTPTransportProxyFunc) GenericOption { - return newGenericOption(func(cfg Config) Config { - cfg.Metrics.Proxy = pf - return cfg - }) -} diff --git a/internal/shared/otlp/otlpmetric/oconf/options_test.go.tmpl b/internal/shared/otlp/otlpmetric/oconf/options_test.go.tmpl deleted file mode 100644 index f266628e2ae..00000000000 --- a/internal/shared/otlp/otlpmetric/oconf/options_test.go.tmpl +++ /dev/null @@ -1,583 +0,0 @@ -// Code created by gotmpl. DO NOT MODIFY. -// source: internal/shared/otlp/otlpmetric/oconf/options_test.go.tmpl - -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package oconf - -import ( - "errors" - "net/http" - "net/url" - "testing" - "time" - - "github.com/stretchr/testify/assert" - - "{{ .envconfigImportPath }}" - "go.opentelemetry.io/otel/sdk/metric" - "go.opentelemetry.io/otel/sdk/metric/metricdata" -) - -const ( - WeakCertificate = ` ------BEGIN CERTIFICATE----- -MIIBhzCCASygAwIBAgIRANHpHgAWeTnLZpTSxCKs0ggwCgYIKoZIzj0EAwIwEjEQ -MA4GA1UEChMHb3RlbC1nbzAeFw0yMTA0MDExMzU5MDNaFw0yMTA0MDExNDU5MDNa -MBIxEDAOBgNVBAoTB290ZWwtZ28wWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAS9 -nWSkmPCxShxnp43F+PrOtbGV7sNfkbQ/kxzi9Ego0ZJdiXxkmv/C05QFddCW7Y0Z -sJCLHGogQsYnWJBXUZOVo2MwYTAOBgNVHQ8BAf8EBAMCB4AwEwYDVR0lBAwwCgYI -KwYBBQUHAwEwDAYDVR0TAQH/BAIwADAsBgNVHREEJTAjgglsb2NhbGhvc3SHEAAA -AAAAAAAAAAAAAAAAAAGHBH8AAAEwCgYIKoZIzj0EAwIDSQAwRgIhANwZVVKvfvQ/ -1HXsTvgH+xTQswOwSSKYJ1cVHQhqK7ZbAiEAus8NxpTRnp5DiTMuyVmhVNPB+bVH -Lhnm4N/QDk5rek0= ------END CERTIFICATE----- -` - WeakPrivateKey = ` ------BEGIN PRIVATE KEY----- -MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgN8HEXiXhvByrJ1zK -SFT6Y2l2KqDWwWzKf+t4CyWrNKehRANCAAS9nWSkmPCxShxnp43F+PrOtbGV7sNf -kbQ/kxzi9Ego0ZJdiXxkmv/C05QFddCW7Y0ZsJCLHGogQsYnWJBXUZOV ------END PRIVATE KEY----- -` -) - -type env map[string]string - -func (e *env) getEnv(env string) string { - return (*e)[env] -} - -type fileReader map[string][]byte - -func (f *fileReader) readFile(filename string) ([]byte, error) { - if b, ok := (*f)[filename]; ok { - return b, nil - } - return nil, errors.New("file not found") -} - -func TestConfigs(t *testing.T) { - tlsCert, err := CreateTLSConfig([]byte(WeakCertificate)) - assert.NoError(t, err) - - tests := []struct { - name string - opts []GenericOption - env env - fileReader fileReader - asserts func(t *testing.T, c *Config, grpcOption bool) - }{ - { - name: "Test default configs", - asserts: func(t *testing.T, c *Config, grpcOption bool) { - if grpcOption { - assert.Equal(t, "localhost:4317", c.Metrics.Endpoint) - } else { - assert.Equal(t, "localhost:4318", c.Metrics.Endpoint) - } - assert.Equal(t, NoCompression, c.Metrics.Compression) - assert.Equal(t, map[string]string(nil), c.Metrics.Headers) - assert.Equal(t, 10*time.Second, c.Metrics.Timeout) - }, - }, - - // Endpoint Tests - { - name: "Test With Endpoint", - opts: []GenericOption{ - WithEndpoint("someendpoint"), - }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { - assert.Equal(t, "someendpoint", c.Metrics.Endpoint) - }, - }, - { - name: "Test With Endpoint URL", - opts: []GenericOption{ - WithEndpointURL("http://someendpoint/somepath"), - }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { - assert.Equal(t, "someendpoint", c.Metrics.Endpoint) - assert.Equal(t, "/somepath", c.Metrics.URLPath) - assert.Equal(t, true, c.Metrics.Insecure) - }, - }, - { - name: "Test With Secure Endpoint URL", - opts: []GenericOption{ - WithEndpointURL("https://someendpoint/somepath"), - }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { - assert.Equal(t, "someendpoint", c.Metrics.Endpoint) - assert.Equal(t, "/somepath", c.Metrics.URLPath) - assert.Equal(t, false, c.Metrics.Insecure) - }, - }, - { - name: "Test With Invalid Endpoint URL", - opts: []GenericOption{ - WithEndpointURL("%invalid"), - }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { - if grpcOption { - assert.Equal(t, "localhost:4317", c.Metrics.Endpoint) - } else { - assert.Equal(t, "localhost:4318", c.Metrics.Endpoint) - } - assert.Equal(t, "/v1/metrics", c.Metrics.URLPath) - }, - }, - { - name: "Test Environment Endpoint", - env: map[string]string{ - "OTEL_EXPORTER_OTLP_ENDPOINT": "https://env.endpoint/prefix", - }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { - assert.False(t, c.Metrics.Insecure) - if grpcOption { - assert.Equal(t, "env.endpoint/prefix", c.Metrics.Endpoint) - } else { - assert.Equal(t, "env.endpoint", c.Metrics.Endpoint) - assert.Equal(t, "/prefix/v1/metrics", c.Metrics.URLPath) - } - }, - }, - { - name: "Test Environment Signal Specific Endpoint", - env: map[string]string{ - "OTEL_EXPORTER_OTLP_ENDPOINT": "https://overrode.by.signal.specific/env/var", - "OTEL_EXPORTER_OTLP_METRICS_ENDPOINT": "http://env.metrics.endpoint", - }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { - assert.True(t, c.Metrics.Insecure) - assert.Equal(t, "env.metrics.endpoint", c.Metrics.Endpoint) - if !grpcOption { - assert.Equal(t, "/", c.Metrics.URLPath) - } - }, - }, - { - name: "Test Mixed Environment and With Endpoint", - opts: []GenericOption{ - WithEndpoint("metrics_endpoint"), - }, - env: map[string]string{ - "OTEL_EXPORTER_OTLP_ENDPOINT": "env_endpoint", - }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { - assert.Equal(t, "metrics_endpoint", c.Metrics.Endpoint) - }, - }, - { - name: "Test Environment Endpoint with HTTP scheme", - env: map[string]string{ - "OTEL_EXPORTER_OTLP_ENDPOINT": "http://env_endpoint", - }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { - assert.Equal(t, "env_endpoint", c.Metrics.Endpoint) - assert.Equal(t, true, c.Metrics.Insecure) - }, - }, - { - name: "Test Environment Endpoint with HTTP scheme and leading & trailingspaces", - env: map[string]string{ - "OTEL_EXPORTER_OTLP_ENDPOINT": " http://env_endpoint ", - }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { - assert.Equal(t, "env_endpoint", c.Metrics.Endpoint) - assert.Equal(t, true, c.Metrics.Insecure) - }, - }, - { - name: "Test Environment Endpoint with HTTPS scheme", - env: map[string]string{ - "OTEL_EXPORTER_OTLP_ENDPOINT": "https://env_endpoint", - }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { - assert.Equal(t, "env_endpoint", c.Metrics.Endpoint) - assert.Equal(t, false, c.Metrics.Insecure) - }, - }, - { - name: "Test Environment Signal Specific Endpoint with uppercase scheme", - env: map[string]string{ - "OTEL_EXPORTER_OTLP_ENDPOINT": "HTTPS://overrode_by_signal_specific", - "OTEL_EXPORTER_OTLP_METRICS_ENDPOINT": "HtTp://env_metrics_endpoint", - }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { - assert.Equal(t, "env_metrics_endpoint", c.Metrics.Endpoint) - assert.Equal(t, true, c.Metrics.Insecure) - }, - }, - - // Certificate tests - { - name: "Test Default Certificate", - asserts: func(t *testing.T, c *Config, grpcOption bool) { - if grpcOption { - assert.NotNil(t, c.Metrics.GRPCCredentials) - } else { - assert.Nil(t, c.Metrics.TLSCfg) - } - }, - }, - { - name: "Test With Certificate", - opts: []GenericOption{ - WithTLSClientConfig(tlsCert), - }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { - if grpcOption { - // TODO: make sure gRPC's credentials actually works - assert.NotNil(t, c.Metrics.GRPCCredentials) - } else { - // nolint:staticcheck // ignoring tlsCert.RootCAs.Subjects is deprecated ERR because cert does not come from SystemCertPool. - assert.Equal(t, tlsCert.RootCAs.Subjects(), c.Metrics.TLSCfg.RootCAs.Subjects()) - } - }, - }, - { - name: "Test Environment Certificate", - env: map[string]string{ - "OTEL_EXPORTER_OTLP_CERTIFICATE": "cert_path", - }, - fileReader: fileReader{ - "cert_path": []byte(WeakCertificate), - }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { - if grpcOption { - assert.NotNil(t, c.Metrics.GRPCCredentials) - } else { - // nolint:staticcheck // ignoring tlsCert.RootCAs.Subjects is deprecated ERR because cert does not come from SystemCertPool. - assert.Equal(t, tlsCert.RootCAs.Subjects(), c.Metrics.TLSCfg.RootCAs.Subjects()) - } - }, - }, - { - name: "Test Environment Signal Specific Certificate", - env: map[string]string{ - "OTEL_EXPORTER_OTLP_CERTIFICATE": "overrode_by_signal_specific", - "OTEL_EXPORTER_OTLP_METRICS_CERTIFICATE": "cert_path", - }, - fileReader: fileReader{ - "cert_path": []byte(WeakCertificate), - "invalid_cert": []byte("invalid certificate file."), - }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { - if grpcOption { - assert.NotNil(t, c.Metrics.GRPCCredentials) - } else { - // nolint:staticcheck // ignoring tlsCert.RootCAs.Subjects is deprecated ERR because cert does not come from SystemCertPool. - assert.Equal(t, tlsCert.RootCAs.Subjects(), c.Metrics.TLSCfg.RootCAs.Subjects()) - } - }, - }, - { - name: "Test Mixed Environment and With Certificate", - opts: []GenericOption{}, - env: map[string]string{ - "OTEL_EXPORTER_OTLP_CERTIFICATE": "cert_path", - }, - fileReader: fileReader{ - "cert_path": []byte(WeakCertificate), - }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { - if grpcOption { - assert.NotNil(t, c.Metrics.GRPCCredentials) - } else { - // nolint:staticcheck // ignoring tlsCert.RootCAs.Subjects is deprecated ERR because cert does not come from SystemCertPool. - assert.Equal(t, 1, len(c.Metrics.TLSCfg.RootCAs.Subjects())) - } - }, - }, - - // Headers tests - { - name: "Test With Headers", - opts: []GenericOption{ - WithHeaders(map[string]string{"h1": "v1"}), - }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { - assert.Equal(t, map[string]string{"h1": "v1"}, c.Metrics.Headers) - }, - }, - { - name: "Test Environment Headers", - env: map[string]string{"OTEL_EXPORTER_OTLP_HEADERS": "h1=v1,h2=v2"}, - asserts: func(t *testing.T, c *Config, grpcOption bool) { - assert.Equal(t, map[string]string{"h1": "v1", "h2": "v2"}, c.Metrics.Headers) - }, - }, - { - name: "Test Environment Signal Specific Headers", - env: map[string]string{ - "OTEL_EXPORTER_OTLP_HEADERS": "overrode_by_signal_specific", - "OTEL_EXPORTER_OTLP_METRICS_HEADERS": "h1=v1,h2=v2", - }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { - assert.Equal(t, map[string]string{"h1": "v1", "h2": "v2"}, c.Metrics.Headers) - }, - }, - { - name: "Test Mixed Environment and With Headers", - env: map[string]string{"OTEL_EXPORTER_OTLP_HEADERS": "h1=v1,h2=v2"}, - opts: []GenericOption{ - WithHeaders(map[string]string{"m1": "mv1"}), - }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { - assert.Equal(t, map[string]string{"m1": "mv1"}, c.Metrics.Headers) - }, - }, - - // Compression Tests - { - name: "Test With Compression", - opts: []GenericOption{ - WithCompression(GzipCompression), - }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { - assert.Equal(t, GzipCompression, c.Metrics.Compression) - }, - }, - { - name: "Test Environment Compression", - env: map[string]string{ - "OTEL_EXPORTER_OTLP_COMPRESSION": "gzip", - }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { - assert.Equal(t, GzipCompression, c.Metrics.Compression) - }, - }, - { - name: "Test Environment Signal Specific Compression", - env: map[string]string{ - "OTEL_EXPORTER_OTLP_METRICS_COMPRESSION": "gzip", - }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { - assert.Equal(t, GzipCompression, c.Metrics.Compression) - }, - }, - { - name: "Test Mixed Environment and With Compression", - opts: []GenericOption{ - WithCompression(NoCompression), - }, - env: map[string]string{ - "OTEL_EXPORTER_OTLP_METRICS_COMPRESSION": "gzip", - }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { - assert.Equal(t, NoCompression, c.Metrics.Compression) - }, - }, - - // Timeout Tests - { - name: "Test With Timeout", - opts: []GenericOption{ - WithTimeout(time.Duration(5 * time.Second)), - }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { - assert.Equal(t, 5*time.Second, c.Metrics.Timeout) - }, - }, - { - name: "Test Environment Timeout", - env: map[string]string{ - "OTEL_EXPORTER_OTLP_TIMEOUT": "15000", - }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { - assert.Equal(t, c.Metrics.Timeout, 15*time.Second) - }, - }, - { - name: "Test Environment Signal Specific Timeout", - env: map[string]string{ - "OTEL_EXPORTER_OTLP_TIMEOUT": "15000", - "OTEL_EXPORTER_OTLP_METRICS_TIMEOUT": "28000", - }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { - assert.Equal(t, c.Metrics.Timeout, 28*time.Second) - }, - }, - { - name: "Test Mixed Environment and With Timeout", - env: map[string]string{ - "OTEL_EXPORTER_OTLP_TIMEOUT": "15000", - "OTEL_EXPORTER_OTLP_METRICS_TIMEOUT": "28000", - }, - opts: []GenericOption{ - WithTimeout(5 * time.Second), - }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { - assert.Equal(t, c.Metrics.Timeout, 5*time.Second) - }, - }, - - // Temporality Selector Tests - { - name: "WithTemporalitySelector", - opts: []GenericOption{ - WithTemporalitySelector(deltaSelector), - }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { - // Function value comparisons are disallowed, test non-default - // behavior of a TemporalitySelector here to ensure our "catch - // all" was set. - var undefinedKind metric.InstrumentKind - got := c.Metrics.TemporalitySelector - assert.Equal(t, metricdata.DeltaTemporality, got(undefinedKind)) - }, - }, - - // Aggregation Selector Tests - { - name: "WithAggregationSelector", - opts: []GenericOption{ - WithAggregationSelector(dropSelector), - }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { - // Function value comparisons are disallowed, test non-default - // behavior of a AggregationSelector here to ensure our "catch - // all" was set. - var undefinedKind metric.InstrumentKind - got := c.Metrics.AggregationSelector - assert.Equal(t, metric.AggregationDrop{}, got(undefinedKind)) - }, - }, - - // Proxy Tests - { - name: "Test With Proxy", - opts: []GenericOption{ - WithProxy(func(r *http.Request) (*url.URL, error) { - return url.Parse("http://proxy.com") - }), - }, - asserts: func(t *testing.T, c *Config, grpcOption bool) { - assert.NotNil(t, c.Metrics.Proxy) - proxyURL, err := c.Metrics.Proxy(&http.Request{}) - assert.NoError(t, err) - assert.Equal(t, "http://proxy.com", proxyURL.String()) - }, - }, - { - name: "Test Without Proxy", - opts: []GenericOption{}, - asserts: func(t *testing.T, c *Config, grpcOption bool) { - assert.Nil(t, c.Metrics.Proxy) - }, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - origEOR := DefaultEnvOptionsReader - DefaultEnvOptionsReader = envconfig.EnvOptionsReader{ - GetEnv: tt.env.getEnv, - ReadFile: tt.fileReader.readFile, - Namespace: "OTEL_EXPORTER_OTLP", - } - t.Cleanup(func() { DefaultEnvOptionsReader = origEOR }) - - // Tests Generic options as HTTP Options - cfg := NewHTTPConfig(asHTTPOptions(tt.opts)...) - tt.asserts(t, &cfg, false) - - // Tests Generic options as gRPC Options - cfg = NewGRPCConfig(asGRPCOptions(tt.opts)...) - tt.asserts(t, &cfg, true) - }) - } -} - -func dropSelector(metric.InstrumentKind) metric.Aggregation { - return metric.AggregationDrop{} -} - -func deltaSelector(metric.InstrumentKind) metricdata.Temporality { - return metricdata.DeltaTemporality -} - -func asHTTPOptions(opts []GenericOption) []HTTPOption { - converted := make([]HTTPOption, len(opts)) - for i, o := range opts { - converted[i] = NewHTTPOption(o.ApplyHTTPOption) - } - return converted -} - -func asGRPCOptions(opts []GenericOption) []GRPCOption { - converted := make([]GRPCOption, len(opts)) - for i, o := range opts { - converted[i] = NewGRPCOption(o.ApplyGRPCOption) - } - return converted -} - -func TestCleanPath(t *testing.T) { - type args struct { - urlPath string - defaultPath string - } - tests := []struct { - name string - args args - want string - }{ - { - name: "clean empty path", - args: args{ - urlPath: "", - defaultPath: "DefaultPath", - }, - want: "DefaultPath", - }, - { - name: "clean metrics path", - args: args{ - urlPath: "/prefix/v1/metrics", - defaultPath: "DefaultMetricsPath", - }, - want: "/prefix/v1/metrics", - }, - { - name: "clean traces path", - args: args{ - urlPath: "https://env_endpoint", - defaultPath: "DefaultTracesPath", - }, - want: "/https:/env_endpoint", - }, - { - name: "spaces trimmed", - args: args{ - urlPath: " /dir", - }, - want: "/dir", - }, - { - name: "clean path empty", - args: args{ - urlPath: "dir/..", - defaultPath: "DefaultTracesPath", - }, - want: "DefaultTracesPath", - }, - { - name: "make absolute", - args: args{ - urlPath: "dir/a", - }, - want: "/dir/a", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := cleanPath(tt.args.urlPath, tt.args.defaultPath); got != tt.want { - t.Errorf("CleanPath() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/internal/shared/otlp/otlpmetric/otest/client.go.tmpl b/internal/shared/otlp/otlpmetric/otest/client.go.tmpl index 95af8bbff1b..be4de10ba52 100644 --- a/internal/shared/otlp/otlpmetric/otest/client.go.tmpl +++ b/internal/shared/otlp/otlpmetric/otest/client.go.tmpl @@ -19,10 +19,10 @@ import ( "go.opentelemetry.io/otel" semconv "go.opentelemetry.io/otel/semconv/v1.24.0" - collpb "go.opentelemetry.io/proto/otlp/collector/metrics/v1" - cpb "go.opentelemetry.io/proto/otlp/common/v1" - mpb "go.opentelemetry.io/proto/otlp/metrics/v1" - rpb "go.opentelemetry.io/proto/otlp/resource/v1" + collpb "{{ .protoImportPrefix }}/otlp/collector/metrics/v1" + cpb "{{ .protoImportPrefix }}/otlp/common/v1" + mpb "{{ .protoImportPrefix }}/otlp/metrics/v1" + rpb "{{ .protoImportPrefix }}/otlp/resource/v1" ) var ( diff --git a/internal/shared/otlp/otlpmetric/otest/client_test.go.tmpl b/internal/shared/otlp/otlpmetric/otest/client_test.go.tmpl index 79ead4d91f2..dec0a07ddbf 100644 --- a/internal/shared/otlp/otlpmetric/otest/client_test.go.tmpl +++ b/internal/shared/otlp/otlpmetric/otest/client_test.go.tmpl @@ -14,8 +14,8 @@ import ( "{{ .internalImportPath }}" "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/metric/metricdata" - cpb "go.opentelemetry.io/proto/otlp/collector/metrics/v1" - mpb "go.opentelemetry.io/proto/otlp/metrics/v1" + cpb "{{ .protoImportPrefix }}/otlp/collector/metrics/v1" + mpb "{{ .protoImportPrefix }}/otlp/metrics/v1" ) type client struct { diff --git a/internal/shared/otlp/otlpmetric/otest/collector.go.tmpl b/internal/shared/otlp/otlpmetric/otest/collector.go.tmpl deleted file mode 100644 index 22ffd923790..00000000000 --- a/internal/shared/otlp/otlpmetric/otest/collector.go.tmpl +++ /dev/null @@ -1,451 +0,0 @@ -// Code created by gotmpl. DO NOT MODIFY. -// source: internal/shared/otlp/otlpmetric/otest/collector.go.tmpl - -// Copyright The OpenTelemetry Authors -// SPDX-License-Identifier: Apache-2.0 - -package otest - -import ( - "bytes" - "compress/gzip" - "context" - "crypto/ecdsa" - "crypto/elliptic" - "crypto/rand" - "crypto/tls" - "crypto/x509" - "crypto/x509/pkix" // nolint:depguard // This is for testing. - "encoding/pem" - "errors" - "fmt" - "io" - "math/big" - "net" - "net/http" - "net/url" - "sync" - "time" - - "google.golang.org/grpc" - "google.golang.org/grpc/metadata" - "google.golang.org/protobuf/proto" - - "{{ .oconfImportPath }}" - collpb "go.opentelemetry.io/proto/otlp/collector/metrics/v1" - mpb "go.opentelemetry.io/proto/otlp/metrics/v1" -) - -// Collector is the collection target a Client sends metric uploads to. -type Collector interface { - Collect() *Storage -} - -type ExportResult struct { - Response *collpb.ExportMetricsServiceResponse - Err error -} - -// Storage stores uploaded OTLP metric data in their proto form. -type Storage struct { - dataMu sync.Mutex - data []*mpb.ResourceMetrics -} - -// NewStorage returns a configure storage ready to store received requests. -func NewStorage() *Storage { - return &Storage{} -} - -// Add adds the request to the Storage. -func (s *Storage) Add(request *collpb.ExportMetricsServiceRequest) { - s.dataMu.Lock() - defer s.dataMu.Unlock() - s.data = append(s.data, request.ResourceMetrics...) -} - -// Dump returns all added ResourceMetrics and clears the storage. -func (s *Storage) Dump() []*mpb.ResourceMetrics { - s.dataMu.Lock() - defer s.dataMu.Unlock() - - var data []*mpb.ResourceMetrics - data, s.data = s.data, []*mpb.ResourceMetrics{} - return data -} - -// GRPCCollector is an OTLP gRPC server that collects all requests it receives. -type GRPCCollector struct { - collpb.UnimplementedMetricsServiceServer - - headersMu sync.Mutex - headers metadata.MD - storage *Storage - - resultCh <-chan ExportResult - listener net.Listener - srv *grpc.Server -} - -// NewGRPCCollector returns a *GRPCCollector that is listening at the provided -// endpoint. -// -// If endpoint is an empty string, the returned collector will be listening on -// the localhost interface at an OS chosen port. -// -// If errCh is not nil, the collector will respond to Export calls with errors -// sent on that channel. This means that if errCh is not nil Export calls will -// block until an error is received. -func NewGRPCCollector(endpoint string, resultCh <-chan ExportResult) (*GRPCCollector, error) { - if endpoint == "" { - endpoint = "localhost:0" - } - - c := &GRPCCollector{ - storage: NewStorage(), - resultCh: resultCh, - } - - var err error - c.listener, err = net.Listen("tcp", endpoint) - if err != nil { - return nil, err - } - - c.srv = grpc.NewServer() - collpb.RegisterMetricsServiceServer(c.srv, c) - go func() { _ = c.srv.Serve(c.listener) }() - - return c, nil -} - -// Shutdown shuts down the gRPC server closing all open connections and -// listeners immediately. -func (c *GRPCCollector) Shutdown() { c.srv.Stop() } - -// Addr returns the net.Addr c is listening at. -func (c *GRPCCollector) Addr() net.Addr { - return c.listener.Addr() -} - -// Collect returns the Storage holding all collected requests. -func (c *GRPCCollector) Collect() *Storage { - return c.storage -} - -// Headers returns the headers received for all requests. -func (c *GRPCCollector) Headers() map[string][]string { - // Makes a copy. - c.headersMu.Lock() - defer c.headersMu.Unlock() - return metadata.Join(c.headers) -} - -// Export handles the export req. -func (c *GRPCCollector) Export(ctx context.Context, req *collpb.ExportMetricsServiceRequest) (*collpb.ExportMetricsServiceResponse, error) { - c.storage.Add(req) - - if h, ok := metadata.FromIncomingContext(ctx); ok { - c.headersMu.Lock() - c.headers = metadata.Join(c.headers, h) - c.headersMu.Unlock() - } - - if c.resultCh != nil { - r := <-c.resultCh - if r.Response == nil { - return &collpb.ExportMetricsServiceResponse{}, r.Err - } - return r.Response, r.Err - } - return &collpb.ExportMetricsServiceResponse{}, nil -} - -var emptyExportMetricsServiceResponse = func() []byte { - body := collpb.ExportMetricsServiceResponse{} - r, err := proto.Marshal(&body) - if err != nil { - panic(err) - } - return r -}() - -type HTTPResponseError struct { - Err error - Status int - Header http.Header -} - -func (e *HTTPResponseError) Error() string { - return fmt.Sprintf("%d: %s", e.Status, e.Err) -} - -func (e *HTTPResponseError) Unwrap() error { return e.Err } - -// HTTPCollector is an OTLP HTTP server that collects all requests it receives. -type HTTPCollector struct { - plainTextResponse bool - - headersMu sync.Mutex - headers http.Header - storage *Storage - - resultCh <-chan ExportResult - listener net.Listener - srv *http.Server -} - -// NewHTTPCollector returns a *HTTPCollector that is listening at the provided -// endpoint. -// -// If endpoint is an empty string, the returned collector will be listening on -// the localhost interface at an OS chosen port, not use TLS, and listen at the -// default OTLP metric endpoint path ("/v1/metrics"). If the endpoint contains -// a prefix of "https" the server will generate weak self-signed TLS -// certificates and use them to server data. If the endpoint contains a path, -// that path will be used instead of the default OTLP metric endpoint path. -// -// If errCh is not nil, the collector will respond to HTTP requests with errors -// sent on that channel. This means that if errCh is not nil Export calls will -// block until an error is received. -func NewHTTPCollector(endpoint string, resultCh <-chan ExportResult, opts ...func(*HTTPCollector)) (*HTTPCollector, error) { - u, err := url.Parse(endpoint) - if err != nil { - return nil, err - } - if u.Host == "" { - u.Host = "localhost:0" - } - if u.Path == "" { - u.Path = oconf.DefaultMetricsPath - } - - c := &HTTPCollector{ - headers: http.Header{}, - storage: NewStorage(), - resultCh: resultCh, - } - for _, opt := range opts { - opt(c) - } - - c.listener, err = net.Listen("tcp", u.Host) - if err != nil { - return nil, err - } - - mux := http.NewServeMux() - mux.Handle(u.Path, http.HandlerFunc(c.handler)) - c.srv = &http.Server{ - Handler: mux, - ReadTimeout: 10 * time.Second, - WriteTimeout: 10 * time.Second, - } - if u.Scheme == "https" { - cert, err := weakCertificate() - if err != nil { - return nil, err - } - c.srv.TLSConfig = &tls.Config{ - Certificates: []tls.Certificate{cert}, - } - go func() { _ = c.srv.ServeTLS(c.listener, "", "") }() - } else { - go func() { _ = c.srv.Serve(c.listener) }() - } - return c, nil -} - -// WithHTTPCollectorRespondingPlainText makes the HTTPCollector return -// a plaintext, instead of protobuf, response. -func WithHTTPCollectorRespondingPlainText() func(*HTTPCollector) { - return func(s *HTTPCollector) { - s.plainTextResponse = true - } -} - -// Shutdown shuts down the HTTP server closing all open connections and -// listeners. -func (c *HTTPCollector) Shutdown(ctx context.Context) error { - return c.srv.Shutdown(ctx) -} - -// Addr returns the net.Addr c is listening at. -func (c *HTTPCollector) Addr() net.Addr { - return c.listener.Addr() -} - -// Collect returns the Storage holding all collected requests. -func (c *HTTPCollector) Collect() *Storage { - return c.storage -} - -// Headers returns the headers received for all requests. -func (c *HTTPCollector) Headers() map[string][]string { - // Makes a copy. - c.headersMu.Lock() - defer c.headersMu.Unlock() - return c.headers.Clone() -} - -func (c *HTTPCollector) handler(w http.ResponseWriter, r *http.Request) { - c.respond(w, c.record(r)) -} - -func (c *HTTPCollector) record(r *http.Request) ExportResult { - // Currently only supports protobuf. - if v := r.Header.Get("Content-Type"); v != "application/x-protobuf" { - err := fmt.Errorf("content-type not supported: %s", v) - return ExportResult{Err: err} - } - - body, err := c.readBody(r) - if err != nil { - return ExportResult{Err: err} - } - pbRequest := &collpb.ExportMetricsServiceRequest{} - err = proto.Unmarshal(body, pbRequest) - if err != nil { - return ExportResult{ - Err: &HTTPResponseError{ - Err: err, - Status: http.StatusInternalServerError, - }, - } - } - c.storage.Add(pbRequest) - - c.headersMu.Lock() - for k, vals := range r.Header { - for _, v := range vals { - c.headers.Add(k, v) - } - } - c.headersMu.Unlock() - - if c.resultCh != nil { - return <-c.resultCh - } - return ExportResult{Err: err} -} - -func (c *HTTPCollector) readBody(r *http.Request) (body []byte, err error) { - var reader io.ReadCloser - switch r.Header.Get("Content-Encoding") { - case "gzip": - reader, err = gzip.NewReader(r.Body) - if err != nil { - _ = reader.Close() - return nil, &HTTPResponseError{ - Err: err, - Status: http.StatusInternalServerError, - } - } - default: - reader = r.Body - } - - defer func() { - cErr := reader.Close() - if err == nil && cErr != nil { - err = &HTTPResponseError{ - Err: cErr, - Status: http.StatusInternalServerError, - } - } - }() - body, err = io.ReadAll(reader) - if err != nil { - err = &HTTPResponseError{ - Err: err, - Status: http.StatusInternalServerError, - } - } - return body, err -} - -func (c *HTTPCollector) respond(w http.ResponseWriter, resp ExportResult) { - if resp.Err != nil { - w.Header().Set("Content-Type", "text/plain; charset=utf-8") - w.Header().Set("X-Content-Type-Options", "nosniff") - var e *HTTPResponseError - if errors.As(resp.Err, &e) { - for k, vals := range e.Header { - for _, v := range vals { - w.Header().Add(k, v) - } - } - w.WriteHeader(e.Status) - fmt.Fprintln(w, e.Error()) - } else { - w.WriteHeader(http.StatusBadRequest) - fmt.Fprintln(w, resp.Err.Error()) - } - return - } - - if c.plainTextResponse { - w.Header().Set("Content-Type", "text/plain; charset=utf-8") - w.WriteHeader(http.StatusOK) - _, _ = w.Write([]byte("OK")) - return - } - - w.Header().Set("Content-Type", "application/x-protobuf") - w.WriteHeader(http.StatusOK) - if resp.Response == nil { - _, _ = w.Write(emptyExportMetricsServiceResponse) - } else { - r, err := proto.Marshal(resp.Response) - if err != nil { - panic(err) - } - _, _ = w.Write(r) - } -} - -// Based on https://golang.org/src/crypto/tls/generate_cert.go, -// simplified and weakened. -func weakCertificate() (tls.Certificate, error) { - priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - if err != nil { - return tls.Certificate{}, err - } - notBefore := time.Now() - notAfter := notBefore.Add(time.Hour) - max := new(big.Int).Lsh(big.NewInt(1), 128) - sn, err := rand.Int(rand.Reader, max) - if err != nil { - return tls.Certificate{}, err - } - tmpl := x509.Certificate{ - SerialNumber: sn, - Subject: pkix.Name{Organization: []string{"otel-go"}}, - NotBefore: notBefore, - NotAfter: notAfter, - KeyUsage: x509.KeyUsageDigitalSignature, - 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(rand.Reader, &tmpl, &tmpl, &priv.PublicKey, priv) - if err != nil { - return tls.Certificate{}, err - } - var certBuf bytes.Buffer - err = pem.Encode(&certBuf, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) - if err != nil { - return tls.Certificate{}, err - } - privBytes, err := x509.MarshalPKCS8PrivateKey(priv) - if err != nil { - return tls.Certificate{}, err - } - var privBuf bytes.Buffer - err = pem.Encode(&privBuf, &pem.Block{Type: "PRIVATE KEY", Bytes: privBytes}) - if err != nil { - return tls.Certificate{}, err - } - return tls.X509KeyPair(certBuf.Bytes(), privBuf.Bytes()) -} diff --git a/internal/shared/otlp/otlpmetric/transform/attribute.go.tmpl b/internal/shared/otlp/otlpmetric/transform/attribute.go.tmpl index 63cd40dda7a..a244099ee73 100644 --- a/internal/shared/otlp/otlpmetric/transform/attribute.go.tmpl +++ b/internal/shared/otlp/otlpmetric/transform/attribute.go.tmpl @@ -8,7 +8,7 @@ package transform import ( "go.opentelemetry.io/otel/attribute" - cpb "go.opentelemetry.io/proto/otlp/common/v1" + cpb "{{ .protoImportPrefix }}/otlp/common/v1" ) // AttrIter transforms an attribute iterator into OTLP key-values. diff --git a/internal/shared/otlp/otlpmetric/transform/attribute_test.go.tmpl b/internal/shared/otlp/otlpmetric/transform/attribute_test.go.tmpl index 0815ba988d2..77208b1e7e1 100644 --- a/internal/shared/otlp/otlpmetric/transform/attribute_test.go.tmpl +++ b/internal/shared/otlp/otlpmetric/transform/attribute_test.go.tmpl @@ -12,7 +12,7 @@ import ( "github.com/stretchr/testify/assert" "go.opentelemetry.io/otel/attribute" - cpb "go.opentelemetry.io/proto/otlp/common/v1" + cpb "{{ .protoImportPrefix }}/otlp/common/v1" ) var ( diff --git a/internal/shared/otlp/otlpmetric/transform/error.go.tmpl b/internal/shared/otlp/otlpmetric/transform/error.go.tmpl index 69f0dd5d76c..40006b6d251 100644 --- a/internal/shared/otlp/otlpmetric/transform/error.go.tmpl +++ b/internal/shared/otlp/otlpmetric/transform/error.go.tmpl @@ -11,7 +11,7 @@ import ( "fmt" "strings" - mpb "go.opentelemetry.io/proto/otlp/metrics/v1" + mpb "{{ .protoImportPrefix }}/otlp/metrics/v1" ) var ( diff --git a/internal/shared/otlp/otlpmetric/transform/metricdata.go.tmpl b/internal/shared/otlp/otlpmetric/transform/metricdata.go.tmpl index b6d0b76fa1f..4246bca2a66 100644 --- a/internal/shared/otlp/otlpmetric/transform/metricdata.go.tmpl +++ b/internal/shared/otlp/otlpmetric/transform/metricdata.go.tmpl @@ -13,9 +13,9 @@ import ( "time" "go.opentelemetry.io/otel/sdk/metric/metricdata" - cpb "go.opentelemetry.io/proto/otlp/common/v1" - mpb "go.opentelemetry.io/proto/otlp/metrics/v1" - rpb "go.opentelemetry.io/proto/otlp/resource/v1" + cpb "{{ .protoImportPrefix }}/otlp/common/v1" + mpb "{{ .protoImportPrefix }}/otlp/metrics/v1" + rpb "{{ .protoImportPrefix }}/otlp/resource/v1" ) // ResourceMetrics returns an OTLP ResourceMetrics generated from rm. If rm diff --git a/internal/shared/otlp/otlpmetric/transform/metricdata_test.go.tmpl b/internal/shared/otlp/otlpmetric/transform/metricdata_test.go.tmpl index b0bc71e9edb..f7a30b467c2 100644 --- a/internal/shared/otlp/otlpmetric/transform/metricdata_test.go.tmpl +++ b/internal/shared/otlp/otlpmetric/transform/metricdata_test.go.tmpl @@ -18,9 +18,9 @@ import ( "go.opentelemetry.io/otel/sdk/metric/metricdata" "go.opentelemetry.io/otel/sdk/resource" semconv "go.opentelemetry.io/otel/semconv/v1.24.0" - cpb "go.opentelemetry.io/proto/otlp/common/v1" - mpb "go.opentelemetry.io/proto/otlp/metrics/v1" - rpb "go.opentelemetry.io/proto/otlp/resource/v1" + cpb "{{ .protoImportPrefix }}/otlp/common/v1" + mpb "{{ .protoImportPrefix }}/otlp/metrics/v1" + rpb "{{ .protoImportPrefix }}/otlp/resource/v1" ) type unknownAggT struct {