From f36cb458d132c09d1d06fa31bd7dbacd31f4b3e5 Mon Sep 17 00:00:00 2001 From: Pablo Baeyens Date: Wed, 21 Oct 2020 14:11:20 +0200 Subject: [PATCH 01/17] Add EC2 metadata --- exporter/datadogexporter/factory.go | 11 +- exporter/datadogexporter/factory_test.go | 2 + exporter/datadogexporter/go.mod | 3 +- exporter/datadogexporter/metadata/ec2/ec2.go | 80 +++++++++ exporter/datadogexporter/metadata/host.go | 19 +- exporter/datadogexporter/metadata/metadata.go | 168 ++++++++++++++++++ exporter/datadogexporter/metrics_exporter.go | 4 + .../datadogexporter/metrics_exporter_test.go | 1 - exporter/datadogexporter/utils/http.go | 29 ++- 9 files changed, 301 insertions(+), 16 deletions(-) create mode 100644 exporter/datadogexporter/metadata/ec2/ec2.go create mode 100644 exporter/datadogexporter/metadata/metadata.go diff --git a/exporter/datadogexporter/factory.go b/exporter/datadogexporter/factory.go index 9ffe07904881..8bbaddb0847c 100644 --- a/exporter/datadogexporter/factory.go +++ b/exporter/datadogexporter/factory.go @@ -25,6 +25,7 @@ import ( "go.opentelemetry.io/collector/exporter/exporterhelper" "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/datadogexporter/config" + "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/datadogexporter/metadata" ) const ( @@ -79,7 +80,7 @@ func createDefaultConfig() configmodels.Exporter { // createMetricsExporter creates a metrics exporter based on this config. func createMetricsExporter( - _ context.Context, + ctx context.Context, params component.ExporterCreateParams, c configmodels.Exporter, ) (component.MetricsExporter, error) { @@ -96,11 +97,19 @@ func createMetricsExporter( return nil, err } + // Start goroutine for pushing metadata + ctx, cancel := context.WithCancel(ctx) + go metadata.MetadataPusher(ctx, params.Logger, cfg) + return exporterhelper.NewMetricsExporter( cfg, exp.PushMetricsData, exporterhelper.WithQueue(exporterhelper.CreateDefaultQueueSettings()), exporterhelper.WithRetry(exporterhelper.CreateDefaultRetrySettings()), + exporterhelper.WithShutdown(func(context.Context) error { + cancel() + return nil + }), ) } diff --git a/exporter/datadogexporter/factory_test.go b/exporter/datadogexporter/factory_test.go index abd24039b6f3..7838d5d299b2 100644 --- a/exporter/datadogexporter/factory_test.go +++ b/exporter/datadogexporter/factory_test.go @@ -32,6 +32,7 @@ import ( "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/datadogexporter/config" "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/datadogexporter/testutils" + "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/datadogexporter/utils/cache" ) // Test that the factory creates the default configuration @@ -145,6 +146,7 @@ func TestCreateAPIMetricsExporter(t *testing.T) { component.ExporterCreateParams{Logger: logger}, cfg.Exporters["datadog/api"], ) + defer cache.Cache.Delete(cache.CanonicalHostnameKey) assert.NoError(t, err) assert.NotNil(t, exp) diff --git a/exporter/datadogexporter/go.mod b/exporter/datadogexporter/go.mod index 714e7297d002..8704f60241f5 100644 --- a/exporter/datadogexporter/go.mod +++ b/exporter/datadogexporter/go.mod @@ -7,12 +7,13 @@ replace gopkg.in/zorkian/go-datadog-api.v2 v2.29.0 => github.com/zorkian/go-data require ( github.com/DataDog/datadog-agent v0.0.0-20200417180928-f454c60bc16f github.com/DataDog/viper v1.8.0 // indirect + github.com/aws/aws-sdk-go v1.34.9 github.com/census-instrumentation/opencensus-proto v0.3.0 github.com/cihub/seelog v0.0.0-20170130134532-f561c5e57575 // indirect github.com/gogo/protobuf v1.3.1 + github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect github.com/klauspost/compress v1.10.10 github.com/patrickmn/go-cache v2.1.0+incompatible - github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect github.com/stretchr/testify v1.6.1 github.com/zorkian/go-datadog-api v2.29.0+incompatible // indirect go.opencensus.io v0.22.4 diff --git a/exporter/datadogexporter/metadata/ec2/ec2.go b/exporter/datadogexporter/metadata/ec2/ec2.go new file mode 100644 index 000000000000..2d6366b1ba3e --- /dev/null +++ b/exporter/datadogexporter/metadata/ec2/ec2.go @@ -0,0 +1,80 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package ec2 + +import ( + "strings" + + "github.com/aws/aws-sdk-go/aws/ec2metadata" + "github.com/aws/aws-sdk-go/aws/session" + "go.uber.org/zap" +) + +var defaultPrefixes = [3]string{"ip-", "domu", "ec2amaz-"} + +type HostInfo struct { + InstanceID string + EC2Hostname string +} + +// IsDefaultHostname checks if a hostname is an EC2 default +func isDefaultHostname(hostname string) bool { + for _, val := range defaultPrefixes { + if strings.HasPrefix(hostname, val) { + return true + } + } + + return false +} + +// GetHostInfo gets the hostname info from EC2 metadata +func GetHostInfo(logger *zap.Logger) (hostInfo *HostInfo) { + sess, err := session.NewSession() + hostInfo = &HostInfo{} + + if err != nil { + logger.Warn("Failed to build AWS session", zap.Error(err)) + return + } + + meta := ec2metadata.New(sess) + + if !meta.Available() { + logger.Info("EC2 Metadata not available") + return + } + + if idDoc, err := meta.GetInstanceIdentityDocument(); err == nil { + hostInfo.InstanceID = idDoc.InstanceID + } else { + logger.Warn("Failed to get EC2 instance id document", zap.Error(err)) + } + + if ec2Hostname, err := meta.GetMetadata("hostname"); err == nil { + hostInfo.EC2Hostname = ec2Hostname + } else { + logger.Warn("Failed to get EC2 hostname", zap.Error(err)) + } + + return +} + +func (hi *HostInfo) GetHostname(logger *zap.Logger) string { + if isDefaultHostname(hi.EC2Hostname) { + return hi.InstanceID + } + + return hi.EC2Hostname +} diff --git a/exporter/datadogexporter/metadata/host.go b/exporter/datadogexporter/metadata/host.go index 3ceca68e0d6a..faa1979b71ce 100644 --- a/exporter/datadogexporter/metadata/host.go +++ b/exporter/datadogexporter/metadata/host.go @@ -18,14 +18,18 @@ import ( "go.uber.org/zap" "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/datadogexporter/config" + "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/datadogexporter/metadata/ec2" "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/datadogexporter/metadata/system" "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/datadogexporter/metadata/valid" "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/datadogexporter/utils/cache" ) // GetHost gets the hostname according to configuration. -// It gets the configuration hostname and if -// not available it relies on the OS hostname +// It checks in the following order +// 1. Cache +// 2. Configuration +// 3. EC2 instance metadata +// 4. System func GetHost(logger *zap.Logger, cfg *config.Config) *string { if cacheVal, ok := cache.Cache.Get(cache.CanonicalHostnameKey); ok { return cacheVal.(*string) @@ -38,9 +42,14 @@ func GetHost(logger *zap.Logger, cfg *config.Config) *string { logger.Error("Hostname set in configuration is invalid", zap.Error(err)) } - // Get system hostname - hostInfo := system.GetHostInfo(logger) - hostname := hostInfo.GetHostname(logger) + ec2Info := ec2.GetHostInfo(logger) + hostname := ec2Info.GetHostname(logger) + + if hostname == "" { + // Get system hostname + systemInfo := system.GetHostInfo(logger) + hostname = systemInfo.GetHostname(logger) + } if err := valid.Hostname(hostname); err != nil { // If invalid log but continue diff --git a/exporter/datadogexporter/metadata/metadata.go b/exporter/datadogexporter/metadata/metadata.go new file mode 100644 index 000000000000..f58d54ec1cae --- /dev/null +++ b/exporter/datadogexporter/metadata/metadata.go @@ -0,0 +1,168 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package metadata + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "net/http" + "time" + + "go.uber.org/zap" + + "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/datadogexporter/config" + "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/datadogexporter/metadata/ec2" + "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/datadogexporter/metadata/system" + "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/datadogexporter/utils" +) + +// hostMetadata includes metadata about the host tags, +// host aliases and identifies the host as an OpenTelemetry host +type HostMetadata struct { + // Meta includes metadata about the host. + Meta *Meta `json:"meta"` + + // InternalHostname is the canonical hostname + InternalHostname string `json:"internalHostname"` + + // Version is the OpenTelemetry Collector version. + // This is used for correctly identifying the Collector in the backend, + // and for telemetry purposes. + Version string `json:"otel_version"` + + // Flavor is always set to "opentelemetry-collector". + // It is used for telemetry purposes in the backend. + Flavor string `json:"agent-flavor"` + + // Tags includes the host tags + Tags *HostTags `json:"host-tags"` +} + +// hostTags are the host tags. +// Currently only system (configuration) tags are considered. +type HostTags struct { + // OTel are host tags set in the configuration + OTel []string `json:"otel,omitempty"` +} + +// meta includes metadata about the host aliases +type Meta struct { + // InstanceID is the EC2 instance id the Collector is running on, if available + InstanceID string `json:"instance-id,omitempty"` + + // EC2Hostname is the hostname from the EC2 metadata API + EC2Hostname string `json:"ec2-hostname,omitempty"` + + // Hostname is the canonical hostname + Hostname string `json:"hostname"` + + // SocketHostname is the OS hostname + SocketHostname string `json:"socket-hostname,omitempty"` + + // SocketFqdn is the FQDN hostname + SocketFqdn string `json:"socket-fqdn,omitempty"` + + // HostAliases are other available host names + HostAliases []string `json:"host-aliases,omitempty"` +} + +func getHostMetadata(logger *zap.Logger, cfg *config.Config) *HostMetadata { + hostname := *GetHost(logger, cfg) + tags := cfg.Tags + + ec2HostInfo := ec2.GetHostInfo(logger) + systemHostInfo := system.GetHostInfo(logger) + + return &HostMetadata{ + InternalHostname: hostname, + Flavor: utils.Flavor, + Version: utils.Version, + Tags: &HostTags{tags}, + Meta: &Meta{ + InstanceID: ec2HostInfo.InstanceID, + EC2Hostname: ec2HostInfo.EC2Hostname, + Hostname: hostname, + SocketHostname: systemHostInfo.OS, + SocketFqdn: systemHostInfo.FQDN, + }, + } +} + +func pushMetadata(cfg *config.Config, metadata *HostMetadata) error { + path := cfg.Metrics.TCPAddr.Endpoint + "/intake" + buf, _ := json.Marshal(metadata) + req, _ := http.NewRequest(http.MethodPost, path, bytes.NewBuffer(buf)) + utils.SetDDHeaders(req.Header, cfg.API.Key) + utils.SetExtraHeaders(req.Header, utils.JSONHeaders) + client := utils.NewHTTPClient(10 * time.Second) + resp, err := client.Do(req) + + if err != nil { + return err + } + + defer resp.Body.Close() + + if resp.StatusCode >= 400 { + return fmt.Errorf( + "'%d - %s' error when sending metadata payload to %s", + resp.StatusCode, + resp.Status, + path, + ) + } + + return nil +} + +func getAndPushMetadata(logger *zap.Logger, cfg *config.Config) { + const maxRetries = 5 + hostMetadata := getHostMetadata(logger, cfg) + + logger.Debug("Sending host metadata payload", zap.Any("payload", hostMetadata)) + + numRetries, err := utils.DoWithRetries(maxRetries, func() error { + return pushMetadata(cfg, hostMetadata) + }) + + if err != nil { + logger.Warn("Sending host metadata failed", zap.Error(err)) + } else { + logger.Info("Sent host metadata", zap.Int("retries", numRetries)) + } + +} + +// MetadataPusher pushes host metadata payloads periodically to Datadog intake +func MetadataPusher(ctx context.Context, logger *zap.Logger, cfg *config.Config) { + // Push metadata every 30 minutes + ticker := time.NewTicker(30 * time.Minute) + defer ticker.Stop() + defer logger.Debug("Shut down host metadata routine") + + // Run one first time at startup + getAndPushMetadata(logger, cfg) + + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: // Send host metadata + getAndPushMetadata(logger, cfg) + } + } +} diff --git a/exporter/datadogexporter/metrics_exporter.go b/exporter/datadogexporter/metrics_exporter.go index ead7e15cbab0..76fd74f9c1eb 100644 --- a/exporter/datadogexporter/metrics_exporter.go +++ b/exporter/datadogexporter/metrics_exporter.go @@ -16,6 +16,7 @@ package datadogexporter import ( "context" + "time" "go.opentelemetry.io/collector/consumer/pdata" "go.uber.org/zap" @@ -23,6 +24,7 @@ import ( "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/datadogexporter/config" "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/datadogexporter/metadata" + "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/datadogexporter/utils" ) type metricsExporter struct { @@ -49,6 +51,8 @@ func validateAPIKey(logger *zap.Logger, client *datadog.Client) { func newMetricsExporter(logger *zap.Logger, cfg *config.Config) (*metricsExporter, error) { client := datadog.NewClient(cfg.API.Key, "") client.SetBaseUrl(cfg.Metrics.TCPAddr.Endpoint) + client.ExtraHeader["User-Agent"] = utils.UserAgent + client.HttpClient = utils.NewHTTPClient(10 * time.Second) validateAPIKey(logger, client) diff --git a/exporter/datadogexporter/metrics_exporter_test.go b/exporter/datadogexporter/metrics_exporter_test.go index 4e2b6f5e0b93..bdc317d788ce 100644 --- a/exporter/datadogexporter/metrics_exporter_test.go +++ b/exporter/datadogexporter/metrics_exporter_test.go @@ -89,7 +89,6 @@ func TestProcessMetrics(t *testing.T) { } exp.processMetrics(metrics) - assert.Equal(t, "test-host", *metrics[0].Host) assert.Equal(t, "test.metric_name", *metrics[0].Metric) assert.ElementsMatch(t, diff --git a/exporter/datadogexporter/utils/http.go b/exporter/datadogexporter/utils/http.go index e7ca004ffe9a..edea5f8d4a60 100644 --- a/exporter/datadogexporter/utils/http.go +++ b/exporter/datadogexporter/utils/http.go @@ -31,6 +31,9 @@ var ( "Content-Type": "application/x-protobuf", "Content-Encoding": "identity", } + Flavor = "opentelemetry-collector-contrib" + Version = "0.13.1" + UserAgent = fmt.Sprintf("%s/%s", Flavor, Version) ) // NewClient returns a http.Client configured with the Agent options. @@ -58,14 +61,24 @@ func SetExtraHeaders(h http.Header, extras map[string]string) { } } +// SetDDHeaders sets the Datadog-specific headers func SetDDHeaders(reqHeader http.Header, apiKey string) { - // userAgent is the computed user agent we'll use when - // communicating with Datadog - var userAgent = fmt.Sprintf( - "%s/%s/%s (+%s)", - "otel-collector-exporter", "0.1", "1", "http://localhost", - ) - reqHeader.Set("DD-Api-Key", apiKey) - reqHeader.Set("User-Agent", userAgent) + reqHeader.Set("User-Agent", UserAgent) +} + +// DoWithRetries repeats a fallible action up to `maxRetries` times +// with exponential backoff +func DoWithRetries(maxRetries int, fn func() error) (n int, err error) { + wait := 1 * time.Second + for i := 0; i < maxRetries; i++ { + err := fn() + if err == nil { + return i, nil + } + time.Sleep(wait) + wait = 2 * wait + } + + return 0, err } From 945b4e6c0954105f5e4c403a967997bbcdacba6d Mon Sep 17 00:00:00 2001 From: Pablo Baeyens Date: Thu, 22 Oct 2020 12:33:55 +0200 Subject: [PATCH 02/17] Add option to disable sending metadata Set it to false in tests to avoid creating goroutines --- exporter/datadogexporter/config/config.go | 3 +++ exporter/datadogexporter/factory.go | 20 +++++++++++++++++--- exporter/datadogexporter/factory_test.go | 7 +++++-- 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/exporter/datadogexporter/config/config.go b/exporter/datadogexporter/config/config.go index 1e9a612eddc0..61757151f86b 100644 --- a/exporter/datadogexporter/config/config.go +++ b/exporter/datadogexporter/config/config.go @@ -141,6 +141,9 @@ type Config struct { // Traces defines the Traces exporter specific configuration Traces TracesConfig `mapstructure:"traces"` + + // SendMetadata defines whether to send host metadata + SendMetadata bool `mapstructure:"send_metadata"` } // Sanitize tries to sanitize a given configuration diff --git a/exporter/datadogexporter/factory.go b/exporter/datadogexporter/factory.go index 8bbaddb0847c..4d87ef6d05d6 100644 --- a/exporter/datadogexporter/factory.go +++ b/exporter/datadogexporter/factory.go @@ -75,6 +75,8 @@ func createDefaultConfig() configmodels.Exporter { Endpoint: "", // set during config sanitization }, }, + + SendMetadata: true, } } @@ -97,9 +99,11 @@ func createMetricsExporter( return nil, err } - // Start goroutine for pushing metadata ctx, cancel := context.WithCancel(ctx) - go metadata.MetadataPusher(ctx, params.Logger, cfg) + if cfg.SendMetadata { + // Start goroutine for pushing metadata + go metadata.MetadataPusher(ctx, params.Logger, cfg) + } return exporterhelper.NewMetricsExporter( cfg, @@ -115,7 +119,7 @@ func createMetricsExporter( // createTraceExporter creates a trace exporter based on this config. func createTraceExporter( - _ context.Context, + ctx context.Context, params component.ExporterCreateParams, c configmodels.Exporter, ) (component.TraceExporter, error) { @@ -136,8 +140,18 @@ func createTraceExporter( return nil, err } + ctx, cancel := context.WithCancel(ctx) + if cfg.SendMetadata { + // Start goroutine for pushing metadata + go metadata.MetadataPusher(ctx, params.Logger, cfg) + } + return exporterhelper.NewTraceExporter( cfg, exp.pushTraceData, + exporterhelper.WithShutdown(func(context.Context) error { + cancel() + return nil + }), ) } diff --git a/exporter/datadogexporter/factory_test.go b/exporter/datadogexporter/factory_test.go index 7838d5d299b2..17f13bcef116 100644 --- a/exporter/datadogexporter/factory_test.go +++ b/exporter/datadogexporter/factory_test.go @@ -32,7 +32,6 @@ import ( "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/datadogexporter/config" "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/datadogexporter/testutils" - "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/datadogexporter/utils/cache" ) // Test that the factory creates the default configuration @@ -58,6 +57,7 @@ func TestCreateDefaultConfig(t *testing.T) { Traces: config.TracesConfig{ SampleRate: 1, }, + SendMetadata: true, }, cfg, "failed to create default config") assert.NoError(t, configcheck.ValidateConfig(cfg)) @@ -111,6 +111,7 @@ func TestLoadConfig(t *testing.T) { Endpoint: "https://trace.agent.datadoghq.eu", }, }, + SendMetadata: true, }, apiConfig) invalidConfig2 := cfg.Exporters["datadog/invalid"].(*config.Config) @@ -138,6 +139,7 @@ func TestCreateAPIMetricsExporter(t *testing.T) { // Use the mock server for API key validation c := (cfg.Exporters["datadog/api"]).(*config.Config) c.Metrics.TCPAddr.Endpoint = server.URL + c.SendMetadata = false cfg.Exporters["datadog/api"] = c ctx := context.Background() @@ -146,7 +148,6 @@ func TestCreateAPIMetricsExporter(t *testing.T) { component.ExporterCreateParams{Logger: logger}, cfg.Exporters["datadog/api"], ) - defer cache.Cache.Delete(cache.CanonicalHostnameKey) assert.NoError(t, err) assert.NotNil(t, exp) @@ -171,6 +172,8 @@ func TestCreateAPITracesExporter(t *testing.T) { require.NotNil(t, cfg) ctx := context.Background() + c := (cfg.Exporters["datadog/api"]).(*config.Config) + c.SendMetadata = false exp, err := factory.CreateTraceExporter( ctx, component.ExporterCreateParams{Logger: logger}, From 7725fd4509c32720f15611192b93b4868373e8f6 Mon Sep 17 00:00:00 2001 From: Pablo Baeyens Date: Thu, 22 Oct 2020 16:07:28 +0200 Subject: [PATCH 03/17] Launch host metadata goroutine only once --- exporter/datadogexporter/config/config.go | 8 ++++++++ exporter/datadogexporter/factory.go | 12 ++++++++---- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/exporter/datadogexporter/config/config.go b/exporter/datadogexporter/config/config.go index 61757151f86b..f978c671c11a 100644 --- a/exporter/datadogexporter/config/config.go +++ b/exporter/datadogexporter/config/config.go @@ -18,6 +18,7 @@ import ( "errors" "fmt" "strings" + "sync" "go.opentelemetry.io/collector/config/configmodels" "go.opentelemetry.io/collector/config/confignet" @@ -144,6 +145,13 @@ type Config struct { // SendMetadata defines whether to send host metadata SendMetadata bool `mapstructure:"send_metadata"` + + // onceMetadata ensures only one exporter (metrics/traces) sends host metadata + onceMetadata sync.Once +} + +func (c *Config) OnceMetadata() *sync.Once { + return &c.onceMetadata } // Sanitize tries to sanitize a given configuration diff --git a/exporter/datadogexporter/factory.go b/exporter/datadogexporter/factory.go index 4d87ef6d05d6..fbb49479e818 100644 --- a/exporter/datadogexporter/factory.go +++ b/exporter/datadogexporter/factory.go @@ -101,8 +101,10 @@ func createMetricsExporter( ctx, cancel := context.WithCancel(ctx) if cfg.SendMetadata { - // Start goroutine for pushing metadata - go metadata.MetadataPusher(ctx, params.Logger, cfg) + once := cfg.OnceMetadata() + once.Do(func() { + go metadata.MetadataPusher(ctx, params.Logger, cfg) + }) } return exporterhelper.NewMetricsExporter( @@ -142,8 +144,10 @@ func createTraceExporter( ctx, cancel := context.WithCancel(ctx) if cfg.SendMetadata { - // Start goroutine for pushing metadata - go metadata.MetadataPusher(ctx, params.Logger, cfg) + once := cfg.OnceMetadata() + once.Do(func() { + go metadata.MetadataPusher(ctx, params.Logger, cfg) + }) } return exporterhelper.NewTraceExporter( From 30580b54b1204d27476cb6ede3d3282965363794 Mon Sep 17 00:00:00 2001 From: Pablo Baeyens Date: Thu, 22 Oct 2020 17:07:47 +0200 Subject: [PATCH 04/17] Add tests for EC2 hostname resolution --- .../datadogexporter/metadata/ec2/ec2_test.go | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 exporter/datadogexporter/metadata/ec2/ec2_test.go diff --git a/exporter/datadogexporter/metadata/ec2/ec2_test.go b/exporter/datadogexporter/metadata/ec2/ec2_test.go new file mode 100644 index 000000000000..3f9f7c5add90 --- /dev/null +++ b/exporter/datadogexporter/metadata/ec2/ec2_test.go @@ -0,0 +1,52 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package ec2 + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "go.uber.org/zap" +) + +const ( + testIP = "ip-12-34-56-78.us-west-2.compute.internal" + testDomu = "domu-12-34-56-78.us-west-2.compute.internal" + testEC2 = "ec2amaz-12-34-56-78.us-west-2.compute.internal" + customHost = "custom-hostname" + testInstanceID = "i-0123456789" +) + +func TestDefaultHostname(t *testing.T) { + assert.True(t, isDefaultHostname(testIP)) + assert.True(t, isDefaultHostname(testDomu)) + assert.True(t, isDefaultHostname(testEC2)) + assert.False(t, isDefaultHostname(customHost)) +} + +func TestGetHostname(t *testing.T) { + logger := zap.NewNop() + + hostInfo := &HostInfo{ + InstanceID: testInstanceID, + EC2Hostname: testIP, + } + assert.Equal(t, testInstanceID, hostInfo.GetHostname(logger)) + + hostInfo = &HostInfo{ + InstanceID: testInstanceID, + EC2Hostname: customHost, + } + assert.Equal(t, customHost, hostInfo.GetHostname(logger)) +} From 309a4ec4cd43844dfe3b1a16b987447b72469f88 Mon Sep 17 00:00:00 2001 From: Pablo Baeyens Date: Thu, 22 Oct 2020 17:53:19 +0200 Subject: [PATCH 05/17] Address linter issues --- exporter/datadogexporter/factory.go | 4 ++-- exporter/datadogexporter/metadata/metadata.go | 4 ++-- exporter/datadogexporter/utils/http.go | 10 +++++----- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/exporter/datadogexporter/factory.go b/exporter/datadogexporter/factory.go index fbb49479e818..9e2ae9757fdc 100644 --- a/exporter/datadogexporter/factory.go +++ b/exporter/datadogexporter/factory.go @@ -103,7 +103,7 @@ func createMetricsExporter( if cfg.SendMetadata { once := cfg.OnceMetadata() once.Do(func() { - go metadata.MetadataPusher(ctx, params.Logger, cfg) + go metadata.Pusher(ctx, params.Logger, cfg) }) } @@ -146,7 +146,7 @@ func createTraceExporter( if cfg.SendMetadata { once := cfg.OnceMetadata() once.Do(func() { - go metadata.MetadataPusher(ctx, params.Logger, cfg) + go metadata.Pusher(ctx, params.Logger, cfg) }) } diff --git a/exporter/datadogexporter/metadata/metadata.go b/exporter/datadogexporter/metadata/metadata.go index f58d54ec1cae..8c8bc101da4e 100644 --- a/exporter/datadogexporter/metadata/metadata.go +++ b/exporter/datadogexporter/metadata/metadata.go @@ -147,8 +147,8 @@ func getAndPushMetadata(logger *zap.Logger, cfg *config.Config) { } -// MetadataPusher pushes host metadata payloads periodically to Datadog intake -func MetadataPusher(ctx context.Context, logger *zap.Logger, cfg *config.Config) { +// Pusher pushes host metadata payloads periodically to Datadog intake +func Pusher(ctx context.Context, logger *zap.Logger, cfg *config.Config) { // Push metadata every 30 minutes ticker := time.NewTicker(30 * time.Minute) defer ticker.Stop() diff --git a/exporter/datadogexporter/utils/http.go b/exporter/datadogexporter/utils/http.go index edea5f8d4a60..469b3681fae5 100644 --- a/exporter/datadogexporter/utils/http.go +++ b/exporter/datadogexporter/utils/http.go @@ -69,16 +69,16 @@ func SetDDHeaders(reqHeader http.Header, apiKey string) { // DoWithRetries repeats a fallible action up to `maxRetries` times // with exponential backoff -func DoWithRetries(maxRetries int, fn func() error) (n int, err error) { +func DoWithRetries(maxRetries int, fn func() error) (i int, err error) { wait := 1 * time.Second - for i := 0; i < maxRetries; i++ { - err := fn() + for i = 0; i < maxRetries; i++ { + err = fn() if err == nil { - return i, nil + return } time.Sleep(wait) wait = 2 * wait } - return 0, err + return } From 00333949a3d4d2317d34b86b3618d413cb67040d Mon Sep 17 00:00:00 2001 From: Pablo Baeyens Date: Thu, 22 Oct 2020 18:07:51 +0200 Subject: [PATCH 06/17] [empty] Retrigger CI From 420546c9973fbcfea1153ae21cc37d04d6c8fc71 Mon Sep 17 00:00:00 2001 From: Pablo Baeyens Date: Fri, 23 Oct 2020 11:21:52 +0200 Subject: [PATCH 07/17] Do not send tags with metrics or traces The backend will add these tags since they are sent with metadata --- exporter/datadogexporter/metrics_exporter.go | 12 +------ .../datadogexporter/metrics_exporter_test.go | 3 +- exporter/datadogexporter/traces_exporter.go | 6 +--- exporter/datadogexporter/translate_traces.go | 32 +++---------------- .../datadogexporter/translate_traces_test.go | 28 ++++++---------- 5 files changed, 17 insertions(+), 64 deletions(-) diff --git a/exporter/datadogexporter/metrics_exporter.go b/exporter/datadogexporter/metrics_exporter.go index 76fd74f9c1eb..44e817c1044c 100644 --- a/exporter/datadogexporter/metrics_exporter.go +++ b/exporter/datadogexporter/metrics_exporter.go @@ -31,7 +31,6 @@ type metricsExporter struct { logger *zap.Logger cfg *config.Config client *datadog.Client - tags []string } func validateAPIKey(logger *zap.Logger, client *datadog.Client) { @@ -56,16 +55,12 @@ func newMetricsExporter(logger *zap.Logger, cfg *config.Config) (*metricsExporte validateAPIKey(logger, client) - // Calculate tags at startup - tags := cfg.TagsConfig.GetTags(false) - - return &metricsExporter{logger, cfg, client, tags}, nil + return &metricsExporter{logger, cfg, client}, nil } func (exp *metricsExporter) processMetrics(metrics []datadog.Metric) { addNamespace := exp.cfg.Metrics.Namespace != "" overrideHostname := exp.cfg.Hostname != "" - addTags := len(exp.tags) > 0 for i := range metrics { if addNamespace { @@ -76,11 +71,6 @@ func (exp *metricsExporter) processMetrics(metrics []datadog.Metric) { if overrideHostname || metrics[i].GetHost() == "" { metrics[i].Host = metadata.GetHost(exp.logger, exp.cfg) } - - if addTags { - metrics[i].Tags = append(metrics[i].Tags, exp.tags...) - } - } } diff --git a/exporter/datadogexporter/metrics_exporter_test.go b/exporter/datadogexporter/metrics_exporter_test.go index bdc317d788ce..67e03707248b 100644 --- a/exporter/datadogexporter/metrics_exporter_test.go +++ b/exporter/datadogexporter/metrics_exporter_test.go @@ -59,6 +59,7 @@ func TestProcessMetrics(t *testing.T) { API: config.APIConfig{ Key: "ddog_32_characters_long_api_key1", }, + // Global tags should be ignored and sent as metadata TagsConfig: config.TagsConfig{ Hostname: "test-host", Env: "test_env", @@ -92,7 +93,7 @@ func TestProcessMetrics(t *testing.T) { assert.Equal(t, "test-host", *metrics[0].Host) assert.Equal(t, "test.metric_name", *metrics[0].Metric) assert.ElementsMatch(t, - []string{"key:val", "env:test_env", "key2:val2"}, + []string{"key2:val2"}, metrics[0].Tags, ) diff --git a/exporter/datadogexporter/traces_exporter.go b/exporter/datadogexporter/traces_exporter.go index 512c652a2829..c546f010fcd9 100644 --- a/exporter/datadogexporter/traces_exporter.go +++ b/exporter/datadogexporter/traces_exporter.go @@ -32,7 +32,6 @@ type traceExporter struct { cfg *config.Config edgeConnection TraceEdgeConnection obfuscator *obfuscate.Obfuscator - tags []string } var ( @@ -58,14 +57,11 @@ func newTraceExporter(logger *zap.Logger, cfg *config.Config) (*traceExporter, e // https://github.com/DataDog/datadog-serverless-functions/blob/11f170eac105d66be30f18eda09eca791bc0d31b/aws/logs_monitoring/trace_forwarder/cmd/trace/main.go#L43 obfuscator := obfuscate.NewObfuscator(obfuscatorConfig) - // Calculate tags at startup - tags := cfg.TagsConfig.GetTags(false) exporter := &traceExporter{ logger: logger, cfg: cfg, edgeConnection: CreateTraceEdgeConnection(cfg.Traces.TCPAddr.Endpoint, cfg.API.Key), obfuscator: obfuscator, - tags: tags, } return exporter, nil @@ -90,7 +86,7 @@ func (exp *traceExporter) pushTraceData( // convert traces to datadog traces and group trace payloads by env // we largely apply the same logic as the serverless implementation, simplified a bit // https://github.com/DataDog/datadog-serverless-functions/blob/f5c3aedfec5ba223b11b76a4239fcbf35ec7d045/aws/logs_monitoring/trace_forwarder/cmd/trace/main.go#L61-L83 - ddTraces, err := ConvertToDatadogTd(td, exp.cfg, exp.tags) + ddTraces, err := ConvertToDatadogTd(td, exp.cfg) if err != nil { exp.logger.Info("failed to convert traces", zap.Error(err)) diff --git a/exporter/datadogexporter/translate_traces.go b/exporter/datadogexporter/translate_traces.go index 105e1595ad40..165e506be956 100644 --- a/exporter/datadogexporter/translate_traces.go +++ b/exporter/datadogexporter/translate_traces.go @@ -69,7 +69,7 @@ var statusCodes = map[int32]codeDetails{ } // converts Traces into an array of datadog trace payloads grouped by env -func ConvertToDatadogTd(td pdata.Traces, cfg *config.Config, globalTags []string) ([]*pb.TracePayload, error) { +func ConvertToDatadogTd(td pdata.Traces, cfg *config.Config) ([]*pb.TracePayload, error) { // get hostname tag // this is getting abstracted out to config // TODO pass logger here once traces code stabilizes @@ -90,7 +90,7 @@ func ConvertToDatadogTd(td pdata.Traces, cfg *config.Config, globalTags []string } // TODO: Also pass in globalTags here when we know what to do with them - payload, err := resourceSpansToDatadogSpans(rs, hostname, cfg, globalTags) + payload, err := resourceSpansToDatadogSpans(rs, hostname, cfg) if err != nil { return traces, err } @@ -129,7 +129,7 @@ func AggregateTracePayloadsByEnv(tracePayloads []*pb.TracePayload) []*pb.TracePa } // converts a Trace's resource spans into a trace payload -func resourceSpansToDatadogSpans(rs pdata.ResourceSpans, hostname string, cfg *config.Config, globalTags []string) (pb.TracePayload, error) { +func resourceSpansToDatadogSpans(rs pdata.ResourceSpans, hostname string, cfg *config.Config) (pb.TracePayload, error) { // get env tag env := cfg.Env @@ -165,7 +165,7 @@ func resourceSpansToDatadogSpans(rs pdata.ResourceSpans, hostname string, cfg *c extractInstrumentationLibraryTags(ils.InstrumentationLibrary(), datadogTags) spans := ils.Spans() for j := 0; j < spans.Len(); j++ { - span, err := spanToDatadogSpan(spans.At(j), resourceServiceName, datadogTags, cfg, globalTags) + span, err := spanToDatadogSpan(spans.At(j), resourceServiceName, datadogTags, cfg) if err != nil { return payload, err @@ -212,7 +212,6 @@ func spanToDatadogSpan(s pdata.Span, serviceName string, datadogTags map[string]string, cfg *config.Config, - globalTags []string, ) (*pb.Span, error) { // otel specification resource service.name takes precedence // and configuration DD_ENV as fallback if it exists @@ -299,16 +298,6 @@ func spanToDatadogSpan(s pdata.Span, setStringTag(span, key, val) } - for _, val := range globalTags { - parts := strings.Split(val, ":") - // only apply global tag if its not service/env/version/host and it is not malformed - if len(parts) < 2 || strings.TrimSpace(parts[1]) == "" || isCanonicalSpanTag(strings.TrimSpace(parts[0])) { - continue - } - - setStringTag(span, strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1])) - } - return span, nil } @@ -477,16 +466,3 @@ func getDatadogResourceName(s pdata.Span, datadogTags map[string]string) string return s.Name() } - -// we want to handle these tags separately -func isCanonicalSpanTag(category string) bool { - switch category { - case - "env", - "host", - "service", - "version": - return true - } - return false -} diff --git a/exporter/datadogexporter/translate_traces_test.go b/exporter/datadogexporter/translate_traces_test.go index b4a1fdfeb80f..bcdb2f9c9630 100644 --- a/exporter/datadogexporter/translate_traces_test.go +++ b/exporter/datadogexporter/translate_traces_test.go @@ -124,7 +124,7 @@ func TestConvertToDatadogTd(t *testing.T) { traces := pdata.NewTraces() traces.ResourceSpans().Resize(1) - outputTraces, err := ConvertToDatadogTd(traces, &config.Config{}, []string{}) + outputTraces, err := ConvertToDatadogTd(traces, &config.Config{}) assert.NoError(t, err) assert.Equal(t, 1, len(outputTraces)) @@ -133,7 +133,7 @@ func TestConvertToDatadogTd(t *testing.T) { func TestConvertToDatadogTdNoResourceSpans(t *testing.T) { traces := pdata.NewTraces() - outputTraces, err := ConvertToDatadogTd(traces, &config.Config{}, []string{}) + outputTraces, err := ConvertToDatadogTd(traces, &config.Config{}) assert.NoError(t, err) assert.Equal(t, 0, len(outputTraces)) @@ -170,7 +170,7 @@ func TestObfuscation(t *testing.T) { ilss.Spans().Resize(1) span.CopyTo(ilss.Spans().At(0)) - outputTraces, err := ConvertToDatadogTd(traces, &config.Config{}, []string{}) + outputTraces, err := ConvertToDatadogTd(traces, &config.Config{}) assert.NoError(t, err) @@ -195,9 +195,8 @@ func TestBasicTracesTranslation(t *testing.T) { // set shouldError and resourceServiceandEnv to false to test defaut behavior rs := NewResourceSpansData(mockTraceID, mockSpanID, mockParentSpanID, false, false) - mockGlobalTags := []string{"global_key:global_value"} // translate mocks to datadog traces - datadogPayload, err := resourceSpansToDatadogSpans(rs, hostname, &config.Config{}, mockGlobalTags) + datadogPayload, err := resourceSpansToDatadogSpans(rs, hostname, &config.Config{}) if err != nil { t.Fatalf("Failed to convert from pdata ResourceSpans to pb.TracePayload: %v", err) @@ -233,7 +232,7 @@ func TestBasicTracesTranslation(t *testing.T) { assert.Equal(t, "web", datadogPayload.Traces[0].Spans[0].Type) // ensure that span.meta and span.metrics pick up attibutes, instrumentation ibrary and resource attribs - assert.Equal(t, 10, len(datadogPayload.Traces[0].Spans[0].Meta)) + assert.Equal(t, 9, len(datadogPayload.Traces[0].Spans[0].Meta)) assert.Equal(t, 1, len(datadogPayload.Traces[0].Spans[0].Metrics)) // ensure that span error is based on otlp span status @@ -263,7 +262,7 @@ func TestTracesTranslationErrorsAndResource(t *testing.T) { rs := NewResourceSpansData(mockTraceID, mockSpanID, mockParentSpanID, true, true) // translate mocks to datadog traces - datadogPayload, err := resourceSpansToDatadogSpans(rs, hostname, &config.Config{}, []string{}) + datadogPayload, err := resourceSpansToDatadogSpans(rs, hostname, &config.Config{}) if err != nil { t.Fatalf("Failed to convert from pdata ResourceSpans to pb.TracePayload: %v", err) @@ -308,7 +307,7 @@ func TestTracesTranslationConfig(t *testing.T) { } // translate mocks to datadog traces - datadogPayload, err := resourceSpansToDatadogSpans(rs, hostname, &cfg, []string{"other_tag:example", "invalidthings"}) + datadogPayload, err := resourceSpansToDatadogSpans(rs, hostname, &cfg) if err != nil { t.Fatalf("Failed to convert from pdata ResourceSpans to pb.TracePayload: %v", err) @@ -329,9 +328,8 @@ func TestTracesTranslationConfig(t *testing.T) { // ensure that env gives resource deployment.environment priority assert.Equal(t, "test-env", datadogPayload.Env) - assert.Equal(t, 14, len(datadogPayload.Traces[0].Spans[0].Meta)) + assert.Equal(t, 13, len(datadogPayload.Traces[0].Spans[0].Meta)) assert.Equal(t, "v1", datadogPayload.Traces[0].Spans[0].Meta["version"]) - assert.Equal(t, "example", datadogPayload.Traces[0].Spans[0].Meta["other_tag"]) } // ensure that the translation returns early if no resource instrumentation library spans @@ -349,7 +347,7 @@ func TestTracesTranslationNoIls(t *testing.T) { } // translate mocks to datadog traces - datadogPayload, err := resourceSpansToDatadogSpans(rs, hostname, &cfg, []string{"other_tag:example", "invalidthings"}) + datadogPayload, err := resourceSpansToDatadogSpans(rs, hostname, &cfg) if err != nil { t.Fatalf("Failed to convert from pdata ResourceSpans to pb.TracePayload: %v", err) @@ -461,14 +459,6 @@ func TestHttpResourceTag(t *testing.T) { assert.Equal(t, "POST", resourceName) } -func TestCanonicalSpanTag(t *testing.T) { - baseCase := isCanonicalSpanTag("notenv") - isCanonicalCase := isCanonicalSpanTag("env") - - assert.Equal(t, false, baseCase) - assert.Equal(t, true, isCanonicalCase) -} - // ensure that payloads get aggregated by env to reduce number of flushes func TestTracePayloadAggr(t *testing.T) { From 2db03acce436bf0913feb207bfe38480615cf196 Mon Sep 17 00:00:00 2001 From: Pablo Baeyens Date: Fri, 23 Oct 2020 17:01:47 +0200 Subject: [PATCH 08/17] Add env to host tags Reusing the `GetTags` function for this since this PR leaves it without use --- exporter/datadogexporter/config/config.go | 26 ++++--------------- .../datadogexporter/config/config_test.go | 14 +++++----- exporter/datadogexporter/metadata/metadata.go | 2 +- 3 files changed, 12 insertions(+), 30 deletions(-) diff --git a/exporter/datadogexporter/config/config.go b/exporter/datadogexporter/config/config.go index c326f91e5a46..7e1975976a37 100644 --- a/exporter/datadogexporter/config/config.go +++ b/exporter/datadogexporter/config/config.go @@ -100,28 +100,12 @@ type TagsConfig struct { Tags []string `mapstructure:"tags"` } -// GetTags gets the default tags extracted from the configuration -func (t *TagsConfig) GetTags(addHost bool) []string { - tags := make([]string, 0, 4) - - vars := map[string]string{ - "env": t.Env, - "service": t.Service, - "version": t.Version, +// GetHostmTags gets the host tags extracted from the configuration +func (t *TagsConfig) GetHostTags() []string { + tags := t.Tags + if t.Env != "none" { + tags = append(tags, fmt.Sprintf("env:%s", t.Env)) } - - if addHost { - vars["host"] = t.Hostname - } - - for name, val := range vars { - if val != "" { - tags = append(tags, fmt.Sprintf("%s:%s", name, val)) - } - } - - tags = append(tags, t.Tags...) - return tags } diff --git a/exporter/datadogexporter/config/config_test.go b/exporter/datadogexporter/config/config_test.go index 9f6ffcb1592e..bcb6ac5f161b 100644 --- a/exporter/datadogexporter/config/config_test.go +++ b/exporter/datadogexporter/config/config_test.go @@ -22,25 +22,23 @@ import ( "go.opentelemetry.io/collector/config/confignet" ) -func TestTags(t *testing.T) { +func TestHostTags(t *testing.T) { tc := TagsConfig{ Hostname: "customhost", Env: "customenv", - Service: "customservice", - Version: "customversion", - Tags: []string{"key1:val1", "key2:val2"}, + // Service and version should be only used for traces + Service: "customservice", + Version: "customversion", + Tags: []string{"key1:val1", "key2:val2"}, } assert.ElementsMatch(t, []string{ - "host:customhost", "env:customenv", - "service:customservice", - "version:customversion", "key1:val1", "key2:val2", }, - tc.GetTags(true), // get host + tc.GetHostTags(), ) } diff --git a/exporter/datadogexporter/metadata/metadata.go b/exporter/datadogexporter/metadata/metadata.go index 8c8bc101da4e..5e795c3a3e4e 100644 --- a/exporter/datadogexporter/metadata/metadata.go +++ b/exporter/datadogexporter/metadata/metadata.go @@ -82,7 +82,7 @@ type Meta struct { func getHostMetadata(logger *zap.Logger, cfg *config.Config) *HostMetadata { hostname := *GetHost(logger, cfg) - tags := cfg.Tags + tags := cfg.GetHostTags() ec2HostInfo := ec2.GetHostInfo(logger) systemHostInfo := system.GetHostInfo(logger) From 5372bb0e86cc3d43755ea9557348fe92f5dfa722 Mon Sep 17 00:00:00 2001 From: Pablo Baeyens Date: Fri, 23 Oct 2020 17:30:04 +0200 Subject: [PATCH 09/17] Fix indentation on go.mod --- exporter/datadogexporter/go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exporter/datadogexporter/go.mod b/exporter/datadogexporter/go.mod index 62951f145456..f4d534024592 100644 --- a/exporter/datadogexporter/go.mod +++ b/exporter/datadogexporter/go.mod @@ -5,7 +5,7 @@ go 1.14 replace gopkg.in/zorkian/go-datadog-api.v2 v2.29.0 => github.com/zorkian/go-datadog-api v2.29.1-0.20201007103024-437d51d487bf+incompatible require ( - github.com/aws/aws-sdk-go v1.34.9 + github.com/aws/aws-sdk-go v1.34.9 github.com/DataDog/datadog-agent/pkg/trace/exportable v0.0.0-20201016145401-4646cf596b02 github.com/gogo/protobuf v1.3.1 github.com/patrickmn/go-cache v2.1.0+incompatible From afb80d07ca8425c83eba4a3e6caf1cb2d6e2e5b1 Mon Sep 17 00:00:00 2001 From: Pablo Baeyens Date: Fri, 23 Oct 2020 17:31:51 +0200 Subject: [PATCH 10/17] Apply suggestions from code review Fix documentation comments Co-authored-by: Kylian Serrania --- exporter/datadogexporter/config/config.go | 2 +- exporter/datadogexporter/metadata/ec2/ec2.go | 2 +- exporter/datadogexporter/metadata/metadata.go | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/exporter/datadogexporter/config/config.go b/exporter/datadogexporter/config/config.go index 7e1975976a37..88278ecc9ac9 100644 --- a/exporter/datadogexporter/config/config.go +++ b/exporter/datadogexporter/config/config.go @@ -100,7 +100,7 @@ type TagsConfig struct { Tags []string `mapstructure:"tags"` } -// GetHostmTags gets the host tags extracted from the configuration +// GetHostTags gets the host tags extracted from the configuration func (t *TagsConfig) GetHostTags() []string { tags := t.Tags if t.Env != "none" { diff --git a/exporter/datadogexporter/metadata/ec2/ec2.go b/exporter/datadogexporter/metadata/ec2/ec2.go index 2d6366b1ba3e..c24f7ac97c6d 100644 --- a/exporter/datadogexporter/metadata/ec2/ec2.go +++ b/exporter/datadogexporter/metadata/ec2/ec2.go @@ -28,7 +28,7 @@ type HostInfo struct { EC2Hostname string } -// IsDefaultHostname checks if a hostname is an EC2 default +// isDefaultHostname checks if a hostname is an EC2 default func isDefaultHostname(hostname string) bool { for _, val := range defaultPrefixes { if strings.HasPrefix(hostname, val) { diff --git a/exporter/datadogexporter/metadata/metadata.go b/exporter/datadogexporter/metadata/metadata.go index 5e795c3a3e4e..f31dff0b7cf6 100644 --- a/exporter/datadogexporter/metadata/metadata.go +++ b/exporter/datadogexporter/metadata/metadata.go @@ -30,7 +30,7 @@ import ( "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/datadogexporter/utils" ) -// hostMetadata includes metadata about the host tags, +// HostMetadata includes metadata about the host tags, // host aliases and identifies the host as an OpenTelemetry host type HostMetadata struct { // Meta includes metadata about the host. @@ -52,14 +52,14 @@ type HostMetadata struct { Tags *HostTags `json:"host-tags"` } -// hostTags are the host tags. +// HostTags are the host tags. // Currently only system (configuration) tags are considered. type HostTags struct { // OTel are host tags set in the configuration OTel []string `json:"otel,omitempty"` } -// meta includes metadata about the host aliases +// Meta includes metadata about the host aliases type Meta struct { // InstanceID is the EC2 instance id the Collector is running on, if available InstanceID string `json:"instance-id,omitempty"` From c517651c6b899f30e19f2821f45b31944adac83e Mon Sep 17 00:00:00 2001 From: Pablo Baeyens Date: Mon, 26 Oct 2020 14:32:09 +0100 Subject: [PATCH 11/17] Use params.ApplicationStartInfo for getting Flavor and Version --- exporter/datadogexporter/factory.go | 8 ++-- exporter/datadogexporter/metadata/metadata.go | 37 ++++++++++--------- exporter/datadogexporter/metrics_exporter.go | 9 +++-- .../datadogexporter/metrics_exporter_test.go | 10 ++--- exporter/datadogexporter/trace_connection.go | 14 ++++--- exporter/datadogexporter/traces_exporter.go | 7 ++-- .../datadogexporter/traces_exporter_test.go | 9 ++--- exporter/datadogexporter/utils/http.go | 13 ++++--- 8 files changed, 58 insertions(+), 49 deletions(-) diff --git a/exporter/datadogexporter/factory.go b/exporter/datadogexporter/factory.go index 81d44c2ce40d..e3d5542650a5 100644 --- a/exporter/datadogexporter/factory.go +++ b/exporter/datadogexporter/factory.go @@ -85,7 +85,7 @@ func createMetricsExporter( return nil, err } - exp, err := newMetricsExporter(params.Logger, cfg) + exp, err := newMetricsExporter(params, cfg) if err != nil { return nil, err } @@ -94,7 +94,7 @@ func createMetricsExporter( if cfg.SendMetadata { once := cfg.OnceMetadata() once.Do(func() { - go metadata.Pusher(ctx, params.Logger, cfg) + go metadata.Pusher(ctx, params, cfg) }) } @@ -124,7 +124,7 @@ func createTraceExporter( return nil, err } - exp, err := newTraceExporter(params.Logger, cfg) + exp, err := newTraceExporter(params, cfg) if err != nil { return nil, err } @@ -133,7 +133,7 @@ func createTraceExporter( if cfg.SendMetadata { once := cfg.OnceMetadata() once.Do(func() { - go metadata.Pusher(ctx, params.Logger, cfg) + go metadata.Pusher(ctx, params, cfg) }) } diff --git a/exporter/datadogexporter/metadata/metadata.go b/exporter/datadogexporter/metadata/metadata.go index f31dff0b7cf6..ab35130bcd3f 100644 --- a/exporter/datadogexporter/metadata/metadata.go +++ b/exporter/datadogexporter/metadata/metadata.go @@ -22,6 +22,7 @@ import ( "net/http" "time" + "go.opentelemetry.io/collector/component" "go.uber.org/zap" "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/datadogexporter/config" @@ -80,17 +81,17 @@ type Meta struct { HostAliases []string `json:"host-aliases,omitempty"` } -func getHostMetadata(logger *zap.Logger, cfg *config.Config) *HostMetadata { - hostname := *GetHost(logger, cfg) +func getHostMetadata(params component.ExporterCreateParams, cfg *config.Config) *HostMetadata { + hostname := *GetHost(params.Logger, cfg) tags := cfg.GetHostTags() - ec2HostInfo := ec2.GetHostInfo(logger) - systemHostInfo := system.GetHostInfo(logger) + ec2HostInfo := ec2.GetHostInfo(params.Logger) + systemHostInfo := system.GetHostInfo(params.Logger) return &HostMetadata{ InternalHostname: hostname, - Flavor: utils.Flavor, - Version: utils.Version, + Flavor: params.ApplicationStartInfo.ExeName, + Version: params.ApplicationStartInfo.Version, Tags: &HostTags{tags}, Meta: &Meta{ InstanceID: ec2HostInfo.InstanceID, @@ -102,11 +103,11 @@ func getHostMetadata(logger *zap.Logger, cfg *config.Config) *HostMetadata { } } -func pushMetadata(cfg *config.Config, metadata *HostMetadata) error { +func pushMetadata(cfg *config.Config, startInfo component.ApplicationStartInfo, metadata *HostMetadata) error { path := cfg.Metrics.TCPAddr.Endpoint + "/intake" buf, _ := json.Marshal(metadata) req, _ := http.NewRequest(http.MethodPost, path, bytes.NewBuffer(buf)) - utils.SetDDHeaders(req.Header, cfg.API.Key) + utils.SetDDHeaders(req.Header, startInfo, cfg.API.Key) utils.SetExtraHeaders(req.Header, utils.JSONHeaders) client := utils.NewHTTPClient(10 * time.Second) resp, err := client.Do(req) @@ -129,40 +130,40 @@ func pushMetadata(cfg *config.Config, metadata *HostMetadata) error { return nil } -func getAndPushMetadata(logger *zap.Logger, cfg *config.Config) { +func getAndPushMetadata(params component.ExporterCreateParams, cfg *config.Config) { const maxRetries = 5 - hostMetadata := getHostMetadata(logger, cfg) + hostMetadata := getHostMetadata(params, cfg) - logger.Debug("Sending host metadata payload", zap.Any("payload", hostMetadata)) + params.Logger.Debug("Sending host metadata payload", zap.Any("payload", hostMetadata)) numRetries, err := utils.DoWithRetries(maxRetries, func() error { - return pushMetadata(cfg, hostMetadata) + return pushMetadata(cfg, params.ApplicationStartInfo, hostMetadata) }) if err != nil { - logger.Warn("Sending host metadata failed", zap.Error(err)) + params.Logger.Warn("Sending host metadata failed", zap.Error(err)) } else { - logger.Info("Sent host metadata", zap.Int("retries", numRetries)) + params.Logger.Info("Sent host metadata", zap.Int("retries", numRetries)) } } // Pusher pushes host metadata payloads periodically to Datadog intake -func Pusher(ctx context.Context, logger *zap.Logger, cfg *config.Config) { +func Pusher(ctx context.Context, params component.ExporterCreateParams, cfg *config.Config) { // Push metadata every 30 minutes ticker := time.NewTicker(30 * time.Minute) defer ticker.Stop() - defer logger.Debug("Shut down host metadata routine") + defer params.Logger.Debug("Shut down host metadata routine") // Run one first time at startup - getAndPushMetadata(logger, cfg) + getAndPushMetadata(params, cfg) for { select { case <-ctx.Done(): return case <-ticker.C: // Send host metadata - getAndPushMetadata(logger, cfg) + getAndPushMetadata(params, cfg) } } } diff --git a/exporter/datadogexporter/metrics_exporter.go b/exporter/datadogexporter/metrics_exporter.go index 44e817c1044c..ecec15fb97c8 100644 --- a/exporter/datadogexporter/metrics_exporter.go +++ b/exporter/datadogexporter/metrics_exporter.go @@ -18,6 +18,7 @@ import ( "context" "time" + "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/consumer/pdata" "go.uber.org/zap" "gopkg.in/zorkian/go-datadog-api.v2" @@ -47,15 +48,15 @@ func validateAPIKey(logger *zap.Logger, client *datadog.Client) { } } -func newMetricsExporter(logger *zap.Logger, cfg *config.Config) (*metricsExporter, error) { +func newMetricsExporter(params component.ExporterCreateParams, cfg *config.Config) (*metricsExporter, error) { client := datadog.NewClient(cfg.API.Key, "") client.SetBaseUrl(cfg.Metrics.TCPAddr.Endpoint) - client.ExtraHeader["User-Agent"] = utils.UserAgent + client.ExtraHeader["User-Agent"] = utils.UserAgent(params.ApplicationStartInfo) client.HttpClient = utils.NewHTTPClient(10 * time.Second) - validateAPIKey(logger, client) + validateAPIKey(params.Logger, client) - return &metricsExporter{logger, cfg, client}, nil + return &metricsExporter{params.Logger, cfg, client}, nil } func (exp *metricsExporter) processMetrics(metrics []datadog.Metric) { diff --git a/exporter/datadogexporter/metrics_exporter_test.go b/exporter/datadogexporter/metrics_exporter_test.go index 67e03707248b..b4415ecd2df7 100644 --- a/exporter/datadogexporter/metrics_exporter_test.go +++ b/exporter/datadogexporter/metrics_exporter_test.go @@ -19,6 +19,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/config/confignet" "go.uber.org/zap" "gopkg.in/zorkian/go-datadog-api.v2" @@ -43,10 +44,10 @@ func TestNewExporter(t *testing.T) { } cfg.Sanitize() - logger := zap.NewNop() + params := component.ExporterCreateParams{Logger: zap.NewNop()} // The client should have been created correctly - exp, err := newMetricsExporter(logger, cfg) + exp, err := newMetricsExporter(params, cfg) require.NoError(t, err) assert.NotNil(t, exp) } @@ -74,9 +75,8 @@ func TestProcessMetrics(t *testing.T) { } cfg.Sanitize() - logger := zap.NewNop() - - exp, err := newMetricsExporter(logger, cfg) + params := component.ExporterCreateParams{Logger: zap.NewNop()} + exp, err := newMetricsExporter(params, cfg) require.NoError(t, err) diff --git a/exporter/datadogexporter/trace_connection.go b/exporter/datadogexporter/trace_connection.go index 1a530a14dbfd..d2a346582fae 100644 --- a/exporter/datadogexporter/trace_connection.go +++ b/exporter/datadogexporter/trace_connection.go @@ -24,6 +24,7 @@ import ( "github.com/DataDog/datadog-agent/pkg/trace/exportable/pb" "github.com/DataDog/datadog-agent/pkg/trace/exportable/stats" "github.com/gogo/protobuf/proto" + "go.opentelemetry.io/collector/component" "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/datadogexporter/utils" ) @@ -38,6 +39,7 @@ type traceEdgeConnection struct { traceURL string statsURL string apiKey string + startInfo component.ApplicationStartInfo InsecureSkipVerify bool } @@ -47,12 +49,13 @@ const ( ) // CreateTraceEdgeConnection returns a new TraceEdgeConnection -func CreateTraceEdgeConnection(rootURL, apiKey string) TraceEdgeConnection { +func CreateTraceEdgeConnection(rootURL, apiKey string, startInfo component.ApplicationStartInfo) TraceEdgeConnection { return &traceEdgeConnection{ - traceURL: rootURL + "/api/v0.2/traces", - statsURL: rootURL + "/api/v0.2/stats", - apiKey: apiKey, + traceURL: rootURL + "/api/v0.2/traces", + statsURL: rootURL + "/api/v0.2/stats", + startInfo: startInfo, + apiKey: apiKey, } } @@ -141,6 +144,7 @@ func (con *traceEdgeConnection) SendStats(ctx context.Context, sts *stats.Payloa // sendPayloadToTraceEdge sends a payload to Trace Edge func (con *traceEdgeConnection) sendPayloadToTraceEdge(ctx context.Context, apiKey string, payload *Payload, url string) (bool, error) { + // Create the request to be sent to the API req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(payload.Bytes)) @@ -148,7 +152,7 @@ func (con *traceEdgeConnection) sendPayloadToTraceEdge(ctx context.Context, apiK return false, err } - utils.SetDDHeaders(req.Header, apiKey) + utils.SetDDHeaders(req.Header, con.startInfo, apiKey) utils.SetExtraHeaders(req.Header, payload.Headers) client := utils.NewHTTPClient(traceEdgeTimeout) diff --git a/exporter/datadogexporter/traces_exporter.go b/exporter/datadogexporter/traces_exporter.go index c546f010fcd9..5528b014894e 100644 --- a/exporter/datadogexporter/traces_exporter.go +++ b/exporter/datadogexporter/traces_exporter.go @@ -21,6 +21,7 @@ import ( "github.com/DataDog/datadog-agent/pkg/trace/exportable/config/configdefs" "github.com/DataDog/datadog-agent/pkg/trace/exportable/obfuscate" "github.com/DataDog/datadog-agent/pkg/trace/exportable/pb" + "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/consumer/pdata" "go.uber.org/zap" @@ -52,15 +53,15 @@ var ( } ) -func newTraceExporter(logger *zap.Logger, cfg *config.Config) (*traceExporter, error) { +func newTraceExporter(params component.ExporterCreateParams, cfg *config.Config) (*traceExporter, error) { // removes potentially sensitive info and PII, approach taken from serverless approach // https://github.com/DataDog/datadog-serverless-functions/blob/11f170eac105d66be30f18eda09eca791bc0d31b/aws/logs_monitoring/trace_forwarder/cmd/trace/main.go#L43 obfuscator := obfuscate.NewObfuscator(obfuscatorConfig) exporter := &traceExporter{ - logger: logger, + logger: params.Logger, cfg: cfg, - edgeConnection: CreateTraceEdgeConnection(cfg.Traces.TCPAddr.Endpoint, cfg.API.Key), + edgeConnection: CreateTraceEdgeConnection(cfg.Traces.TCPAddr.Endpoint, cfg.API.Key, params.ApplicationStartInfo), obfuscator: obfuscator, } diff --git a/exporter/datadogexporter/traces_exporter_test.go b/exporter/datadogexporter/traces_exporter_test.go index 579adee3bc77..3ef37f4a3546 100644 --- a/exporter/datadogexporter/traces_exporter_test.go +++ b/exporter/datadogexporter/traces_exporter_test.go @@ -144,10 +144,9 @@ func testJSONTraceStatsPayload(t *testing.T, rw http.ResponseWriter, req *http.R func TestNewTraceExporter(t *testing.T) { cfg := &config.Config{} cfg.API.Key = "ddog_32_characters_long_api_key1" - logger := zap.NewNop() - + params := component.ExporterCreateParams{Logger: zap.NewNop()} // The client should have been created correctly - exp, err := newTraceExporter(logger, cfg) + exp, err := newTraceExporter(params, cfg) assert.NoError(t, err) assert.NotNil(t, exp) } @@ -175,9 +174,9 @@ func TestPushTraceData(t *testing.T) { }, }, } - logger := zap.NewNop() - exp, err := newTraceExporter(logger, cfg) + params := component.ExporterCreateParams{Logger: zap.NewNop()} + exp, err := newTraceExporter(params, cfg) assert.NoError(t, err) diff --git a/exporter/datadogexporter/utils/http.go b/exporter/datadogexporter/utils/http.go index 469b3681fae5..24e35d213373 100644 --- a/exporter/datadogexporter/utils/http.go +++ b/exporter/datadogexporter/utils/http.go @@ -20,6 +20,8 @@ import ( "net" "net/http" "time" + + "go.opentelemetry.io/collector/component" ) var ( @@ -31,9 +33,6 @@ var ( "Content-Type": "application/x-protobuf", "Content-Encoding": "identity", } - Flavor = "opentelemetry-collector-contrib" - Version = "0.13.1" - UserAgent = fmt.Sprintf("%s/%s", Flavor, Version) ) // NewClient returns a http.Client configured with the Agent options. @@ -61,10 +60,14 @@ func SetExtraHeaders(h http.Header, extras map[string]string) { } } +func UserAgent(startInfo component.ApplicationStartInfo) string { + return fmt.Sprintf("%s/%s", startInfo.ExeName, startInfo.Version) +} + // SetDDHeaders sets the Datadog-specific headers -func SetDDHeaders(reqHeader http.Header, apiKey string) { +func SetDDHeaders(reqHeader http.Header, startInfo component.ApplicationStartInfo, apiKey string) { reqHeader.Set("DD-Api-Key", apiKey) - reqHeader.Set("User-Agent", UserAgent) + reqHeader.Set("User-Agent", UserAgent(startInfo)) } // DoWithRetries repeats a fallible action up to `maxRetries` times From 7d12b91024c7df44e4e6a02da9c0a9052571788e Mon Sep 17 00:00:00 2001 From: Pablo Baeyens Date: Tue, 27 Oct 2020 10:18:42 +0100 Subject: [PATCH 12/17] [empty] Retrigger CI From aebcfbf2297eb5e6260011c2d1bbd7965a02bcda Mon Sep 17 00:00:00 2001 From: Pablo Baeyens Date: Tue, 27 Oct 2020 10:41:08 +0100 Subject: [PATCH 13/17] [empty] Retrigger CI again From 50dbb8f181abf1a1046475c46b7c94a541dfc50d Mon Sep 17 00:00:00 2001 From: Pablo Baeyens Date: Tue, 27 Oct 2020 16:24:20 +0100 Subject: [PATCH 14/17] [empty] Retrigger CI (third attempt) From 37d63012e73594e0be41d4449d8ae6c35c423495 Mon Sep 17 00:00:00 2001 From: Pablo Baeyens Date: Tue, 27 Oct 2020 17:37:30 +0100 Subject: [PATCH 15/17] [empty] Retrigger CI (4) From 3edd53f084862e38eefd47a36ef9ab29615b58b7 Mon Sep 17 00:00:00 2001 From: Pablo Baeyens Date: Tue, 27 Oct 2020 18:01:22 +0100 Subject: [PATCH 16/17] Fix after merge --- exporter/datadogexporter/translate_traces.go | 1 - 1 file changed, 1 deletion(-) diff --git a/exporter/datadogexporter/translate_traces.go b/exporter/datadogexporter/translate_traces.go index ec4264f4a970..5caf6af7f193 100644 --- a/exporter/datadogexporter/translate_traces.go +++ b/exporter/datadogexporter/translate_traces.go @@ -19,7 +19,6 @@ import ( "fmt" "net/http" "strconv" - "strings" "github.com/DataDog/datadog-agent/pkg/trace/exportable/pb" "go.opencensus.io/trace" From 21b0e7b4f16822bc7494989b565e25c8ed132fa5 Mon Sep 17 00:00:00 2001 From: Pablo Baeyens Date: Wed, 28 Oct 2020 12:39:55 +0100 Subject: [PATCH 17/17] Improve test coverage --- .../datadogexporter/metadata/metadata_test.go | 101 ++++++++++++++++++ exporter/datadogexporter/utils/http_test.go | 55 ++++++++++ 2 files changed, 156 insertions(+) create mode 100644 exporter/datadogexporter/metadata/metadata_test.go create mode 100644 exporter/datadogexporter/utils/http_test.go diff --git a/exporter/datadogexporter/metadata/metadata_test.go b/exporter/datadogexporter/metadata/metadata_test.go new file mode 100644 index 000000000000..2f210dd12a27 --- /dev/null +++ b/exporter/datadogexporter/metadata/metadata_test.go @@ -0,0 +1,101 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package metadata + +import ( + "encoding/json" + "io/ioutil" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/component" + "go.uber.org/zap" + + "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/datadogexporter/config" + "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/datadogexporter/utils/cache" +) + +func TestGetHostMetadata(t *testing.T) { + cache.Cache.Flush() + params := component.ExporterCreateParams{ + Logger: zap.NewNop(), + ApplicationStartInfo: component.ApplicationStartInfo{ + ExeName: "otelcontribcol", + Version: "1.0", + }, + } + + cfg := &config.Config{TagsConfig: config.TagsConfig{ + Hostname: "hostname", + Env: "prod", + Tags: []string{"key1:tag1", "key2:tag2"}, + }} + + metadata := getHostMetadata(params, cfg) + + assert.Equal(t, metadata.InternalHostname, "hostname") + assert.Equal(t, metadata.Flavor, "otelcontribcol") + assert.Equal(t, metadata.Version, "1.0") + assert.Equal(t, metadata.Meta.Hostname, "hostname") + assert.ElementsMatch(t, metadata.Tags.OTel, []string{"key1:tag1", "key2:tag2", "env:prod"}) +} + +func TestPushMetadata(t *testing.T) { + + cfg := &config.Config{API: config.APIConfig{Key: "apikey"}} + + startInfo := component.ApplicationStartInfo{ + ExeName: "otelcontribcol", + Version: "1.0", + } + + metadata := HostMetadata{ + InternalHostname: "hostname", + Flavor: "otelcontribcol", + Version: "1.0", + Tags: &HostTags{OTel: []string{"key1:val1"}}, + Meta: &Meta{ + InstanceID: "i-XXXXXXXXXX", + EC2Hostname: "ip-123-45-67-89", + Hostname: "hostname", + SocketHostname: "ip-123-45-67-89", + SocketFqdn: "ip-123-45-67-89.internal", + }, + } + + handler := http.NewServeMux() + handler.HandleFunc("/intake", func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, r.Header.Get("DD-Api-Key"), "apikey") + assert.Equal(t, r.Header.Get("User-Agent"), "otelcontribcol/1.0") + + body, err := ioutil.ReadAll(r.Body) + require.NoError(t, err) + + var recvMetadata HostMetadata + err = json.Unmarshal(body, &recvMetadata) + require.NoError(t, err) + assert.Equal(t, metadata, recvMetadata) + }) + + ts := httptest.NewServer(handler) + defer ts.Close() + cfg.Metrics.Endpoint = ts.URL + + err := pushMetadata(cfg, startInfo, &metadata) + require.NoError(t, err) +} diff --git a/exporter/datadogexporter/utils/http_test.go b/exporter/datadogexporter/utils/http_test.go new file mode 100644 index 000000000000..fc6745a4cb9a --- /dev/null +++ b/exporter/datadogexporter/utils/http_test.go @@ -0,0 +1,55 @@ +// Copyright The OpenTelemetry Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package utils + +import ( + "errors" + "net/http" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/component" +) + +var ( + startInfo = component.ApplicationStartInfo{ + ExeName: "otelcontribcol", + Version: "1.0", + } +) + +func TestUserAgent(t *testing.T) { + + assert.Equal(t, UserAgent(startInfo), "otelcontribcol/1.0") +} + +func TestDDHeaders(t *testing.T) { + header := http.Header{} + apiKey := "apikey" + SetDDHeaders(header, startInfo, apiKey) + assert.Equal(t, header.Get("DD-Api-Key"), apiKey) + assert.Equal(t, header.Get("USer-Agent"), "otelcontribcol/1.0") + +} + +func TestDoWithRetries(t *testing.T) { + i, err := DoWithRetries(3, func() error { return nil }) + require.NoError(t, err) + assert.Equal(t, i, 0) + + _, err = DoWithRetries(1, func() error { return errors.New("action failed") }) + require.Error(t, err) +}