Skip to content

Commit

Permalink
[exporter/datadog] Decouple Config structs from internal components (
Browse files Browse the repository at this point in the history
…open-telemetry#8375)

* [exporter/datadog] Remove reference to `Config` on `GetHost`

* [exporter/datadog] Remove reference to Config on ProcessMetrics

* [exporter/datadog] Decouple metadata pusher configuration from main configuration

* `fd -e go --base-directory exporter/datadogexporter -X sd mcfg pcfg`
  • Loading branch information
mx-psi authored and David Pequegnot committed Apr 8, 2022
1 parent 09e29b9 commit f0510cb
Show file tree
Hide file tree
Showing 13 changed files with 127 additions and 75 deletions.
4 changes: 2 additions & 2 deletions exporter/datadogexporter/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ func createMetricsExporter(
if md.ResourceMetrics().Len() > 0 {
attrs = md.ResourceMetrics().At(0).Resource().Attributes()
}
go metadata.Pusher(ctx, set, cfg, attrs)
go metadata.Pusher(ctx, set, newMetadataConfigfromConfig(cfg), attrs)
})
return nil
}
Expand Down Expand Up @@ -190,7 +190,7 @@ func createTracesExporter(
if td.ResourceSpans().Len() > 0 {
attrs = td.ResourceSpans().At(0).Resource().Attributes()
}
go metadata.Pusher(ctx, set, cfg, attrs)
go metadata.Pusher(ctx, set, newMetadataConfigfromConfig(cfg), attrs)
})
return nil
}
Expand Down
34 changes: 34 additions & 0 deletions exporter/datadogexporter/hostmetadata.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// 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:https://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 datadogexporter // import "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/datadogexporter"

import (
"github.com/open-telemetry/opentelemetry-collector-contrib/exporter/datadogexporter/config"
"github.com/open-telemetry/opentelemetry-collector-contrib/exporter/datadogexporter/internal/metadata"
)

// newMetadataConfigfromConfig creates a new metadata pusher config from the main config.
func newMetadataConfigfromConfig(cfg *config.Config) metadata.PusherConfig {
return metadata.PusherConfig{
ConfigHostname: cfg.Hostname,
ConfigTags: cfg.GetHostTags(),
MetricsEndpoint: cfg.Metrics.Endpoint,
APIKey: cfg.API.Key,
UseResourceMetadata: cfg.UseResourceMetadata,
InsecureSkipVerify: cfg.TLSSetting.InsecureSkipVerify,
TimeoutSettings: cfg.TimeoutSettings,
RetrySettings: cfg.RetrySettings,
}
}
39 changes: 39 additions & 0 deletions exporter/datadogexporter/internal/metadata/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// 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:https://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 "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/datadogexporter/internal/metadata"

import (
"go.opentelemetry.io/collector/exporter/exporterhelper"
)

// PusherConfig is the configuration for the metadata pusher goroutine.
type PusherConfig struct {
// ConfigHosthame is the hostname set in the configuration of the exporter (empty if unset).
ConfigHostname string
// ConfigTags are the tags set in the configuration of the exporter (empty if unset).
ConfigTags []string
// MetricsEndpoint is the metrics endpoint.
MetricsEndpoint string
// APIKey is the API key set in configuration.
APIKey string
// UseResourceMetadata is the value of 'use_resource_metadata' on the top-level configuration.
UseResourceMetadata bool
// InsecureSkipVerify is the value of `tls.insecure_skip_verify` on the configuration.
InsecureSkipVerify bool
// TimeoutSettings of exporter.
TimeoutSettings exporterhelper.TimeoutSettings
// RetrySettings of exporter.
RetrySettings exporterhelper.RetrySettings
}
7 changes: 3 additions & 4 deletions exporter/datadogexporter/internal/metadata/host.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ package metadata // import "github.com/open-telemetry/opentelemetry-collector-co
import (
"go.uber.org/zap"

"github.com/open-telemetry/opentelemetry-collector-contrib/exporter/datadogexporter/config"
"github.com/open-telemetry/opentelemetry-collector-contrib/exporter/datadogexporter/internal/metadata/ec2"
"github.com/open-telemetry/opentelemetry-collector-contrib/exporter/datadogexporter/internal/metadata/system"
"github.com/open-telemetry/opentelemetry-collector-contrib/exporter/datadogexporter/internal/metadata/valid"
Expand All @@ -30,9 +29,9 @@ import (
// 2. Cache
// 3. EC2 instance metadata
// 4. System
func GetHost(logger *zap.Logger, cfg *config.Config) string {
if cfg.Hostname != "" {
return cfg.Hostname
func GetHost(logger *zap.Logger, configHostname string) string {
if configHostname != "" {
return configHostname
}

if cacheVal, ok := cache.Cache.Get(cache.CanonicalHostnameKey); ok {
Expand Down
11 changes: 3 additions & 8 deletions exporter/datadogexporter/internal/metadata/host_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import (
"github.com/stretchr/testify/require"
"go.uber.org/zap"

"github.com/open-telemetry/opentelemetry-collector-contrib/exporter/datadogexporter/config"
"github.com/open-telemetry/opentelemetry-collector-contrib/exporter/datadogexporter/internal/utils/cache"
)

Expand All @@ -34,15 +33,11 @@ func TestHost(t *testing.T) {
// if the cache key is already set.
cache.Cache.Delete(cache.CanonicalHostnameKey)

host := GetHost(logger, &config.Config{
TagsConfig: config.TagsConfig{Hostname: "test-host"},
})
host := GetHost(logger, "test-host")
assert.Equal(t, host, "test-host")

// config.Config.Hostname does not get stored in the cache
host = GetHost(logger, &config.Config{
TagsConfig: config.TagsConfig{Hostname: "test-host-2"},
})
host = GetHost(logger, "test-host-2")
assert.Equal(t, host, "test-host-2")

// Disable EC2 Metadata service to prevent fetching hostname from there,
Expand All @@ -53,7 +48,7 @@ func TestHost(t *testing.T) {
require.NoError(t, err)
defer os.Setenv(awsEc2MetadataDisabled, curr)

host = GetHost(logger, &config.Config{})
host = GetHost(logger, "")
osHostname, err := os.Hostname()
require.NoError(t, err)
// TODO: Investigate why the returned host contains more data on github actions.
Expand Down
31 changes: 15 additions & 16 deletions exporter/datadogexporter/internal/metadata/metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ import (
conventions "go.opentelemetry.io/collector/model/semconv/v1.6.1"
"go.uber.org/zap"

"github.com/open-telemetry/opentelemetry-collector-contrib/exporter/datadogexporter/config"
"github.com/open-telemetry/opentelemetry-collector-contrib/exporter/datadogexporter/internal/metadata/ec2"
"github.com/open-telemetry/opentelemetry-collector-contrib/exporter/datadogexporter/internal/metadata/system"
"github.com/open-telemetry/opentelemetry-collector-contrib/exporter/datadogexporter/internal/model/attributes"
Expand Down Expand Up @@ -120,10 +119,10 @@ func metadataFromAttributes(attrs pdata.AttributeMap) *HostMetadata {
return hm
}

func fillHostMetadata(params component.ExporterCreateSettings, cfg *config.Config, hm *HostMetadata) {
func fillHostMetadata(params component.ExporterCreateSettings, pcfg PusherConfig, hm *HostMetadata) {
// Could not get hostname from attributes
if hm.InternalHostname == "" {
hostname := GetHost(params.Logger, cfg)
hostname := GetHost(params.Logger, pcfg.ConfigHostname)
hm.InternalHostname = hostname
hm.Meta.Hostname = hostname
}
Expand All @@ -132,7 +131,7 @@ func fillHostMetadata(params component.ExporterCreateSettings, cfg *config.Confi
// since it does not come from OTEL conventions
hm.Flavor = params.BuildInfo.Command
hm.Version = params.BuildInfo.Version
hm.Tags.OTel = append(hm.Tags.OTel, cfg.GetHostTags()...)
hm.Tags.OTel = append(hm.Tags.OTel, pcfg.ConfigTags...)

// EC2 data was not set from attributes
if hm.Meta.EC2Hostname == "" {
Expand All @@ -149,19 +148,19 @@ func fillHostMetadata(params component.ExporterCreateSettings, cfg *config.Confi
}
}

func pushMetadata(cfg *config.Config, params component.ExporterCreateSettings, metadata *HostMetadata) error {
func pushMetadata(pcfg PusherConfig, params component.ExporterCreateSettings, metadata *HostMetadata) error {
if metadata.Meta.Hostname == "" {
// if the hostname is empty, don't send metadata; we don't need it.
params.Logger.Debug("Skipping host metadata since the hostname is empty")
return nil
}

path := cfg.Metrics.TCPAddr.Endpoint + "/intake"
path := pcfg.MetricsEndpoint + "/intake"
buf, _ := json.Marshal(metadata)
req, _ := http.NewRequest(http.MethodPost, path, bytes.NewBuffer(buf))
utils.SetDDHeaders(req.Header, params.BuildInfo, cfg.API.Key)
utils.SetDDHeaders(req.Header, params.BuildInfo, pcfg.APIKey)
utils.SetExtraHeaders(req.Header, utils.JSONHeaders)
client := utils.NewHTTPClient(cfg.TimeoutSettings, cfg.LimitedHTTPClientSettings)
client := utils.NewHTTPClient(pcfg.TimeoutSettings, pcfg.InsecureSkipVerify)
resp, err := client.Do(req)

if err != nil {
Expand All @@ -181,11 +180,11 @@ func pushMetadata(cfg *config.Config, params component.ExporterCreateSettings, m
return nil
}

func pushMetadataWithRetry(retrier *utils.Retrier, params component.ExporterCreateSettings, cfg *config.Config, hostMetadata *HostMetadata) {
func pushMetadataWithRetry(retrier *utils.Retrier, params component.ExporterCreateSettings, pcfg PusherConfig, hostMetadata *HostMetadata) {
params.Logger.Debug("Sending host metadata payload", zap.Any("payload", hostMetadata))

err := retrier.DoWithRetries(context.Background(), func(context.Context) error {
return pushMetadata(cfg, params, hostMetadata)
return pushMetadata(pcfg, params, hostMetadata)
})

if err != nil {
Expand All @@ -197,12 +196,12 @@ func pushMetadataWithRetry(retrier *utils.Retrier, params component.ExporterCrea
}

// Pusher pushes host metadata payloads periodically to Datadog intake
func Pusher(ctx context.Context, params component.ExporterCreateSettings, cfg *config.Config, attrs pdata.AttributeMap) {
func Pusher(ctx context.Context, params component.ExporterCreateSettings, pcfg PusherConfig, attrs pdata.AttributeMap) {
// Push metadata every 30 minutes
ticker := time.NewTicker(30 * time.Minute)
defer ticker.Stop()
defer params.Logger.Debug("Shut down host metadata routine")
retrier := utils.NewRetrier(params.Logger, cfg.RetrySettings, scrub.NewScrubber())
retrier := utils.NewRetrier(params.Logger, pcfg.RetrySettings, scrub.NewScrubber())

// Get host metadata from resources and fill missing info using our exporter.
// Currently we only retrieve it once but still send the same payload
Expand All @@ -212,20 +211,20 @@ func Pusher(ctx context.Context, params component.ExporterCreateSettings, cfg *c
// do not change over time. If this ever changes `hostMetadata`
// *must* be deep copied before calling `fillHostMetadata`.
hostMetadata := &HostMetadata{Meta: &Meta{}, Tags: &HostTags{}}
if cfg.UseResourceMetadata {
if pcfg.UseResourceMetadata {
hostMetadata = metadataFromAttributes(attrs)
}
fillHostMetadata(params, cfg, hostMetadata)
fillHostMetadata(params, pcfg, hostMetadata)

// Run one first time at startup
pushMetadataWithRetry(retrier, params, cfg, hostMetadata)
pushMetadataWithRetry(retrier, params, pcfg, hostMetadata)

for {
select {
case <-ctx.Done():
return
case <-ticker.C: // Send host metadata
pushMetadataWithRetry(retrier, params, cfg, hostMetadata)
pushMetadataWithRetry(retrier, params, pcfg, hostMetadata)
}
}
}
39 changes: 20 additions & 19 deletions exporter/datadogexporter/internal/metadata/metadata_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import (
"go.opentelemetry.io/collector/component/componenttest"
conventions "go.opentelemetry.io/collector/model/semconv/v1.6.1"

"github.com/open-telemetry/opentelemetry-collector-contrib/exporter/datadogexporter/config"
"github.com/open-telemetry/opentelemetry-collector-contrib/exporter/datadogexporter/internal/model/attributes"
"github.com/open-telemetry/opentelemetry-collector-contrib/exporter/datadogexporter/internal/model/attributes/azure"
"github.com/open-telemetry/opentelemetry-collector-contrib/exporter/datadogexporter/internal/testutils"
Expand Down Expand Up @@ -67,14 +66,13 @@ func TestFillHostMetadata(t *testing.T) {
params := componenttest.NewNopExporterCreateSettings()
params.BuildInfo = mockBuildInfo

cfg := &config.Config{TagsConfig: config.TagsConfig{
Hostname: "hostname",
Env: "prod",
Tags: []string{"key1:tag1", "key2:tag2"},
}}
pcfg := PusherConfig{
ConfigHostname: "hostname",
ConfigTags: []string{"key1:tag1", "key2:tag2", "env:prod"},
}

metadata := &HostMetadata{Meta: &Meta{}, Tags: &HostTags{}}
fillHostMetadata(params, cfg, metadata)
fillHostMetadata(params, pcfg, metadata)

assert.Equal(t, metadata.InternalHostname, "hostname")
assert.Equal(t, metadata.Flavor, "otelcontribcol")
Expand All @@ -88,7 +86,7 @@ func TestFillHostMetadata(t *testing.T) {
Tags: &HostTags{},
}

fillHostMetadata(params, cfg, metadataWithVals)
fillHostMetadata(params, pcfg, metadataWithVals)
assert.Equal(t, metadataWithVals.InternalHostname, "my-custom-hostname")
assert.Equal(t, metadataWithVals.Flavor, "otelcontribcol")
assert.Equal(t, metadataWithVals.Version, "1.0")
Expand Down Expand Up @@ -156,7 +154,9 @@ func TestMetadataFromAttributes(t *testing.T) {
}

func TestPushMetadata(t *testing.T) {
cfg := &config.Config{API: config.APIConfig{Key: "apikey"}}
pcfg := PusherConfig{
APIKey: "apikey",
}

handler := http.NewServeMux()
handler.HandleFunc("/intake", func(w http.ResponseWriter, r *http.Request) {
Expand All @@ -174,29 +174,30 @@ func TestPushMetadata(t *testing.T) {

ts := httptest.NewServer(handler)
defer ts.Close()
cfg.Metrics.Endpoint = ts.URL
pcfg.MetricsEndpoint = ts.URL

err := pushMetadata(cfg, mockExporterCreateSettings, &mockMetadata)
err := pushMetadata(pcfg, mockExporterCreateSettings, &mockMetadata)
require.NoError(t, err)
}

func TestFailPushMetadata(t *testing.T) {
cfg := &config.Config{API: config.APIConfig{Key: "apikey"}}

pcfg := PusherConfig{
APIKey: "apikey",
}
handler := http.NewServeMux()
handler.Handle("/intake", http.NotFoundHandler())

ts := httptest.NewServer(handler)
defer ts.Close()
cfg.Metrics.Endpoint = ts.URL
pcfg.MetricsEndpoint = ts.URL

err := pushMetadata(cfg, mockExporterCreateSettings, &mockMetadata)
err := pushMetadata(pcfg, mockExporterCreateSettings, &mockMetadata)
require.Error(t, err)
}

func TestPusher(t *testing.T) {
cfg := &config.Config{
API: config.APIConfig{Key: "apikey"},
pcfg := PusherConfig{
APIKey: "apikey",
UseResourceMetadata: true,
}
params := componenttest.NewNopExporterCreateSettings()
Expand All @@ -210,9 +211,9 @@ func TestPusher(t *testing.T) {

server := testutils.DatadogServerMock()
defer server.Close()
cfg.Metrics.Endpoint = server.URL
pcfg.MetricsEndpoint = server.URL

go Pusher(ctx, params, cfg, attrs)
go Pusher(ctx, params, pcfg, attrs)

body := <-server.MetadataChan
var recvMetadata HostMetadata
Expand Down
4 changes: 1 addition & 3 deletions exporter/datadogexporter/internal/metrics/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ import (

"go.opentelemetry.io/collector/component"
"gopkg.in/zorkian/go-datadog-api.v2"

"github.com/open-telemetry/opentelemetry-collector-contrib/exporter/datadogexporter/config"
)

type MetricDataType string
Expand Down Expand Up @@ -94,7 +92,7 @@ func DefaultMetrics(exporterType string, hostname string, timestamp uint64, buil

// ProcessMetrics adds the hostname to the metric and prefixes it with the "otel"
// namespace as the Datadog backend expects
func ProcessMetrics(ms []datadog.Metric, cfg *config.Config) {
func ProcessMetrics(ms []datadog.Metric) {
addNamespace(ms, otelNamespacePrefix)
}

Expand Down
Loading

0 comments on commit f0510cb

Please sign in to comment.