123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558 |
- // Copyright The OpenTelemetry Authors
- // SPDX-License-Identifier: Apache-2.0
- package signalfxexporter
- import (
- "net/url"
- "path/filepath"
- "testing"
- "time"
- "github.com/cenkalti/backoff/v4"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
- "go.opentelemetry.io/collector/component"
- "go.opentelemetry.io/collector/config/confighttp"
- "go.opentelemetry.io/collector/config/configopaque"
- "go.opentelemetry.io/collector/confmap"
- "go.opentelemetry.io/collector/confmap/confmaptest"
- "go.opentelemetry.io/collector/exporter/exporterhelper"
- "go.uber.org/zap"
- apmcorrelation "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/signalfxexporter/internal/apm/correlations"
- "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/signalfxexporter/internal/correlation"
- "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/signalfxexporter/internal/metadata"
- "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/signalfxexporter/internal/translation"
- "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/signalfxexporter/internal/translation/dpfilters"
- "github.com/open-telemetry/opentelemetry-collector-contrib/internal/splunk"
- )
- func TestLoadConfig(t *testing.T) {
- t.Parallel()
- cm, err := confmaptest.LoadConf(filepath.Join("testdata", "config.yaml"))
- require.NoError(t, err)
- seventy := 70
- hundred := 100
- idleConnTimeout := 30 * time.Second
- tests := []struct {
- id component.ID
- expected *Config
- }{
- {
- id: component.NewIDWithName(metadata.Type, ""),
- expected: &Config{
- AccessToken: "testToken",
- Realm: "ap0",
- HTTPClientSettings: confighttp.HTTPClientSettings{
- Timeout: 10 * time.Second,
- Headers: nil,
- MaxIdleConns: &hundred,
- MaxIdleConnsPerHost: &hundred,
- IdleConnTimeout: &idleConnTimeout,
- },
- RetrySettings: exporterhelper.RetrySettings{
- Enabled: true,
- InitialInterval: 5 * time.Second,
- MaxInterval: 30 * time.Second,
- MaxElapsedTime: 5 * time.Minute,
- RandomizationFactor: backoff.DefaultRandomizationFactor,
- Multiplier: backoff.DefaultMultiplier,
- },
- QueueSettings: exporterhelper.NewDefaultQueueSettings(),
- AccessTokenPassthroughConfig: splunk.AccessTokenPassthroughConfig{
- AccessTokenPassthrough: true,
- },
- LogDimensionUpdates: false,
- DimensionClient: DimensionClientConfig{
- MaxBuffered: 10000,
- SendDelay: 10 * time.Second,
- MaxIdleConns: 20,
- MaxIdleConnsPerHost: 20,
- MaxConnsPerHost: 20,
- IdleConnTimeout: 30 * time.Second,
- Timeout: 10 * time.Second,
- },
- TranslationRules: nil,
- ExcludeMetrics: nil,
- IncludeMetrics: nil,
- DeltaTranslationTTL: 3600,
- ExcludeProperties: nil,
- Correlation: &correlation.Config{
- HTTPClientSettings: confighttp.HTTPClientSettings{
- Endpoint: "",
- Timeout: 5 * time.Second,
- },
- StaleServiceTimeout: 5 * time.Minute,
- SyncAttributes: map[string]string{
- "k8s.pod.uid": "k8s.pod.uid",
- "container.id": "container.id",
- },
- Config: apmcorrelation.Config{
- MaxRequests: 20,
- MaxBuffered: 10_000,
- MaxRetries: 2,
- LogUpdates: false,
- RetryDelay: 30 * time.Second,
- CleanupInterval: 1 * time.Minute,
- },
- },
- NonAlphanumericDimensionChars: "_-.",
- },
- },
- {
- id: component.NewIDWithName(metadata.Type, "allsettings"),
- expected: &Config{
- AccessToken: "testToken",
- Realm: "us1",
- HTTPClientSettings: confighttp.HTTPClientSettings{
- Timeout: 2 * time.Second,
- Headers: map[string]configopaque.String{
- "added-entry": "added value",
- "dot.test": "test",
- },
- MaxIdleConns: &seventy,
- MaxIdleConnsPerHost: &seventy,
- IdleConnTimeout: &idleConnTimeout,
- },
- RetrySettings: exporterhelper.RetrySettings{
- Enabled: true,
- InitialInterval: 10 * time.Second,
- MaxInterval: 1 * time.Minute,
- MaxElapsedTime: 10 * time.Minute,
- RandomizationFactor: backoff.DefaultRandomizationFactor,
- Multiplier: backoff.DefaultMultiplier,
- },
- QueueSettings: exporterhelper.QueueSettings{
- Enabled: true,
- NumConsumers: 2,
- QueueSize: 10,
- }, AccessTokenPassthroughConfig: splunk.AccessTokenPassthroughConfig{
- AccessTokenPassthrough: false,
- },
- LogDimensionUpdates: true,
- DimensionClient: DimensionClientConfig{
- MaxBuffered: 1,
- SendDelay: time.Hour,
- MaxIdleConns: 100,
- MaxIdleConnsPerHost: 10,
- MaxConnsPerHost: 10000,
- IdleConnTimeout: 2 * time.Hour,
- Timeout: 20 * time.Second,
- },
- TranslationRules: []translation.Rule{
- {
- Action: translation.ActionRenameDimensionKeys,
- Mapping: map[string]string{
- "k8s.cluster.name": "kubernetes_cluster",
- },
- },
- {
- Action: translation.ActionDropDimensions,
- DimensionPairs: map[string]map[string]bool{
- "foo": nil,
- "foo1": {"bar": true},
- },
- },
- {
- Action: translation.ActionDropDimensions,
- MetricName: "metric",
- DimensionPairs: map[string]map[string]bool{
- "foo": nil,
- "foo1": {"bar": true},
- },
- },
- {
- Action: translation.ActionDropDimensions,
- MetricNames: map[string]bool{
- "metric1": true,
- "metric2": true,
- },
- DimensionPairs: map[string]map[string]bool{
- "foo": nil,
- "foo1": {"bar": true},
- },
- },
- },
- ExcludeMetrics: []dpfilters.MetricFilter{
- {
- MetricName: "metric1",
- },
- {
- MetricNames: []string{"metric2", "metric3"},
- },
- {
- MetricName: "metric4",
- Dimensions: map[string]any{
- "dimension_key": "dimension_val",
- },
- },
- {
- MetricName: "metric5",
- Dimensions: map[string]any{
- "dimension_key": []any{"dimension_val1", "dimension_val2"},
- },
- },
- {
- MetricName: `/cpu\..*/`,
- },
- {
- MetricNames: []string{"cpu.util*", "memory.util*"},
- },
- {
- MetricName: "cpu.utilization",
- Dimensions: map[string]any{
- "container_name": "/^[A-Z][A-Z]$/",
- },
- },
- },
- IncludeMetrics: []dpfilters.MetricFilter{
- {
- MetricName: "metric1",
- },
- {
- MetricNames: []string{"metric2", "metric3"},
- },
- },
- DeltaTranslationTTL: 3600,
- ExcludeProperties: []dpfilters.PropertyFilter{
- {
- PropertyName: mustStringFilter(t, "globbed*"),
- },
- {
- PropertyValue: mustStringFilter(t, "!globbed*value"),
- },
- {
- DimensionName: mustStringFilter(t, "globbed*"),
- },
- {
- DimensionValue: mustStringFilter(t, "!globbed*value"),
- },
- {
- PropertyName: mustStringFilter(t, "globbed*"),
- PropertyValue: mustStringFilter(t, "!globbed*value"),
- DimensionName: mustStringFilter(t, "globbed*"),
- DimensionValue: mustStringFilter(t, "!globbed*value"),
- },
- },
- Correlation: &correlation.Config{
- HTTPClientSettings: confighttp.HTTPClientSettings{
- Endpoint: "",
- Timeout: 5 * time.Second,
- },
- StaleServiceTimeout: 5 * time.Minute,
- SyncAttributes: map[string]string{
- "k8s.pod.uid": "k8s.pod.uid",
- "container.id": "container.id",
- },
- Config: apmcorrelation.Config{
- MaxRequests: 20,
- MaxBuffered: 10_000,
- MaxRetries: 2,
- LogUpdates: false,
- RetryDelay: 30 * time.Second,
- CleanupInterval: 1 * time.Minute,
- },
- },
- NonAlphanumericDimensionChars: "_-.",
- },
- },
- }
- for _, tt := range tests {
- t.Run(tt.id.String(), func(t *testing.T) {
- factory := NewFactory()
- cfg := factory.CreateDefaultConfig()
- sub, err := cm.Sub(tt.id.String())
- require.NoError(t, err)
- require.NoError(t, component.UnmarshalConfig(sub, cfg))
- assert.NoError(t, component.ValidateConfig(cfg))
- // We need to add the default exclude rules.
- assert.NoError(t, setDefaultExcludes(tt.expected))
- assert.Equal(t, tt.expected, cfg)
- })
- }
- }
- func TestConfigGetMetricTranslator(t *testing.T) {
- tests := []struct {
- name string
- cfg *Config
- want *translation.MetricTranslator
- wantErr bool
- }{
- {
- name: "Test empty config",
- cfg: &Config{
- DeltaTranslationTTL: 3600,
- },
- want: func() *translation.MetricTranslator {
- translator, err := translation.NewMetricTranslator(defaultTranslationRules, 3600)
- require.NoError(t, err)
- return translator
- }(),
- },
- {
- name: "Test empty rules",
- cfg: &Config{
- TranslationRules: []translation.Rule{},
- DeltaTranslationTTL: 3600,
- },
- want: func() *translation.MetricTranslator {
- translator, err := translation.NewMetricTranslator([]translation.Rule{}, 3600)
- require.NoError(t, err)
- return translator
- }(),
- },
- {
- name: "Test disable rules",
- cfg: &Config{
- DisableDefaultTranslationRules: true,
- DeltaTranslationTTL: 3600,
- },
- want: func() *translation.MetricTranslator {
- translator, err := translation.NewMetricTranslator([]translation.Rule{}, 3600)
- require.NoError(t, err)
- return translator
- }(),
- },
- {
- name: "Test disable rules overrides rules",
- cfg: &Config{
- TranslationRules: []translation.Rule{{Action: translation.ActionDropDimensions}},
- DisableDefaultTranslationRules: true,
- DeltaTranslationTTL: 3600,
- },
- want: func() *translation.MetricTranslator {
- translator, err := translation.NewMetricTranslator([]translation.Rule{}, 3600)
- require.NoError(t, err)
- return translator
- }(),
- },
- {
- name: "Test invalid translation rules",
- cfg: &Config{
- Realm: "us0",
- AccessToken: "access_token",
- TranslationRules: []translation.Rule{
- {
- Action: translation.ActionRenameDimensionKeys,
- },
- },
- DeltaTranslationTTL: 3600,
- },
- wantErr: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.cfg.getMetricTranslator(zap.NewNop())
- if tt.wantErr {
- assert.Error(t, err)
- return
- }
- require.NoError(t, err)
- assert.Equal(t, tt.want, got)
- })
- }
- }
- func TestConfigGetIngestURL(t *testing.T) {
- tests := []struct {
- name string
- cfg *Config
- want *url.URL
- wantErr bool
- }{
- {
- name: "Test URL from Realm",
- cfg: &Config{
- Realm: "us0",
- },
- want: &url.URL{
- Scheme: "https",
- Host: "ingest.us0.signalfx.com",
- Path: "",
- },
- },
- {
- name: "Test URL overrides",
- cfg: &Config{
- Realm: "us0",
- IngestURL: "https://ingest.us1.signalfx.com/",
- },
- want: &url.URL{
- Scheme: "https",
- Host: "ingest.us1.signalfx.com",
- Path: "/",
- },
- },
- {
- name: "Test invalid URL",
- cfg: &Config{
- IngestURL: "https://api us1 signalfx com/",
- },
- wantErr: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.cfg.getIngestURL()
- if tt.wantErr {
- assert.Error(t, err)
- return
- }
- require.NoError(t, err)
- assert.Equal(t, tt.want, got)
- })
- }
- }
- func TestConfigGetAPIURL(t *testing.T) {
- tests := []struct {
- name string
- cfg *Config
- want *url.URL
- wantErr bool
- }{
- {
- name: "Test URL from Realm",
- cfg: &Config{
- Realm: "us0",
- },
- want: &url.URL{
- Scheme: "https",
- Host: "api.us0.signalfx.com",
- },
- },
- {
- name: "Test URL overrides",
- cfg: &Config{
- Realm: "us0",
- APIURL: "https://api.us1.signalfx.com/",
- },
- want: &url.URL{
- Scheme: "https",
- Host: "api.us1.signalfx.com",
- Path: "/",
- },
- },
- {
- name: "Test invalid URL",
- cfg: &Config{
- APIURL: "https://api us1 signalfx com/",
- },
- wantErr: true,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- got, err := tt.cfg.getAPIURL()
- if tt.wantErr {
- assert.Error(t, err)
- return
- }
- require.NoError(t, err)
- assert.Equal(t, tt.want, got)
- })
- }
- }
- func TestConfigValidateErrors(t *testing.T) {
- tests := []struct {
- name string
- cfg *Config
- }{
- {
- name: "Test empty config",
- cfg: &Config{},
- },
- {
- name: "Test empty realm and API URL",
- cfg: &Config{
- AccessToken: "access_token",
- IngestURL: "https://ingest.us1.signalfx.com/",
- },
- },
- {
- name: "Test empty realm and Ingest URL",
- cfg: &Config{
- AccessToken: "access_token",
- APIURL: "https://api.us1.signalfx.com/",
- },
- },
- {
- name: "Negative Timeout",
- cfg: &Config{
- Realm: "us0",
- AccessToken: "access_token",
- HTTPClientSettings: confighttp.HTTPClientSettings{Timeout: -1 * time.Second},
- },
- },
- {
- name: "Negative QueueSize",
- cfg: &Config{
- Realm: "us0",
- AccessToken: "access_token",
- QueueSettings: exporterhelper.QueueSettings{
- Enabled: true,
- QueueSize: -1,
- },
- },
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- assert.Error(t, component.ValidateConfig(tt.cfg))
- })
- }
- }
- func TestUnmarshalExcludeMetrics(t *testing.T) {
- tests := []struct {
- name string
- cfg *Config
- excludeMetricsLen int
- }{
- {
- name: "empty config",
- cfg: &Config{},
- excludeMetricsLen: 12,
- },
- {
- name: "existing exclude config",
- cfg: &Config{
- ExcludeMetrics: []dpfilters.MetricFilter{
- {
- MetricNames: []string{"metric1"},
- },
- },
- },
- excludeMetricsLen: 13,
- },
- {
- name: "existing empty exclude config",
- cfg: &Config{
- ExcludeMetrics: []dpfilters.MetricFilter{},
- },
- excludeMetricsLen: 0,
- },
- }
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- require.NoError(t, tt.cfg.Unmarshal(confmap.NewFromStringMap(map[string]any{})))
- assert.Len(t, tt.cfg.ExcludeMetrics, tt.excludeMetricsLen)
- })
- }
- }
- func mustStringFilter(t *testing.T, filter string) *dpfilters.StringFilter {
- sf, err := dpfilters.NewStringFilter([]string{filter})
- require.NoError(t, err)
- return sf
- }
|