From 69f0517c6e38c62b905a67c81150e4d388ff2c0f Mon Sep 17 00:00:00 2001 From: David Sevcik Date: Mon, 9 Dec 2024 18:45:39 +0100 Subject: [PATCH 01/41] Extension --- cmd/solarwinds-otel-collector/components.go | 2 + cmd/solarwinds-otel-collector/go.mod | 3 + exporter/solarwindsexporter/README.md | 6 +- exporter/solarwindsexporter/config.go | 64 +----- exporter/solarwindsexporter/config_test.go | 103 +--------- exporter/solarwindsexporter/factory.go | 16 +- .../generated_component_test.go | 14 -- .../generated_package_test.go | 14 -- .../internal/metadata/generated_status.go | 14 -- exporter/solarwindsexporter/metadata.yaml | 4 - .../solarwindsexporter/solarwinds_exporter.go | 125 ++++++++---- .../solarwindsexporter/testdata/full.yaml | 4 +- .../solarwindsexporter/testdata/minimal.yaml | 3 +- .../testdata/missing_dc.yaml | 1 - .../testdata/missing_token.yaml | 1 - .../testdata/url_override.yaml | 4 - extension/solarwindsextension/Makefile | 7 + extension/solarwindsextension/README.md | 11 + extension/solarwindsextension/doc.go | 2 + .../solarwindsextension/endpoint_config.go | 27 +++ extension/solarwindsextension/extension.go | 64 ++++++ extension/solarwindsextension/factory.go | 26 +++ .../generated_component_test.go | 49 +++++ .../generated_package_test.go | 12 ++ extension/solarwindsextension/go.mod | 74 +++++++ extension/solarwindsextension/go.sum | 188 ++++++++++++++++++ .../solarwindsextension/internal/config.go | 140 +++++++++++++ .../solarwindsextension/internal/exporter.go | 75 +++++++ .../solarwindsextension/internal/heartbeat.go | 91 +++++++++ .../internal/metadata/generated_status.go | 16 ++ .../internal/uptime_counter.go | 16 ++ .../internal/uptime_metric.go | 34 ++++ extension/solarwindsextension/metadata.yaml | 12 ++ 33 files changed, 969 insertions(+), 253 deletions(-) delete mode 100644 exporter/solarwindsexporter/testdata/missing_dc.yaml delete mode 100644 exporter/solarwindsexporter/testdata/missing_token.yaml delete mode 100644 exporter/solarwindsexporter/testdata/url_override.yaml create mode 100644 extension/solarwindsextension/Makefile create mode 100644 extension/solarwindsextension/README.md create mode 100644 extension/solarwindsextension/doc.go create mode 100644 extension/solarwindsextension/endpoint_config.go create mode 100644 extension/solarwindsextension/extension.go create mode 100644 extension/solarwindsextension/factory.go create mode 100644 extension/solarwindsextension/generated_component_test.go create mode 100644 extension/solarwindsextension/generated_package_test.go create mode 100644 extension/solarwindsextension/go.mod create mode 100644 extension/solarwindsextension/go.sum create mode 100644 extension/solarwindsextension/internal/config.go create mode 100644 extension/solarwindsextension/internal/exporter.go create mode 100644 extension/solarwindsextension/internal/heartbeat.go create mode 100644 extension/solarwindsextension/internal/metadata/generated_status.go create mode 100644 extension/solarwindsextension/internal/uptime_counter.go create mode 100644 extension/solarwindsextension/internal/uptime_metric.go create mode 100644 extension/solarwindsextension/metadata.yaml diff --git a/cmd/solarwinds-otel-collector/components.go b/cmd/solarwinds-otel-collector/components.go index fed480e..2293a17 100644 --- a/cmd/solarwinds-otel-collector/components.go +++ b/cmd/solarwinds-otel-collector/components.go @@ -17,6 +17,7 @@ package main import ( "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/fileexporter" "github.com/solarwinds/solarwinds-otel-collector/exporter/solarwindsexporter" + "github.com/solarwinds/solarwinds-otel-collector/extension/solarwindsextension" "go.opentelemetry.io/collector/exporter/debugexporter" "go.opentelemetry.io/collector/exporter/nopexporter" "go.opentelemetry.io/collector/exporter/otlpexporter" @@ -219,6 +220,7 @@ func components() (otelcol.Factories, error) { sigv4authextension.NewFactory(), solarwindsapmsettingsextension.NewFactory(), sumologicextension.NewFactory(), + solarwindsextension.NewFactory(), ) if err != nil { diff --git a/cmd/solarwinds-otel-collector/go.mod b/cmd/solarwinds-otel-collector/go.mod index c23d4bc..85ab6e7 100644 --- a/cmd/solarwinds-otel-collector/go.mod +++ b/cmd/solarwinds-otel-collector/go.mod @@ -161,6 +161,7 @@ require ( github.com/open-telemetry/opentelemetry-collector-contrib/receiver/zipkinreceiver v0.113.0 github.com/open-telemetry/opentelemetry-collector-contrib/receiver/zookeeperreceiver v0.113.0 github.com/solarwinds/solarwinds-otel-collector/exporter/solarwindsexporter v0.113.0 + github.com/solarwinds/solarwinds-otel-collector/extension/solarwindsextension v0.113.0 go.opentelemetry.io/collector/component v0.113.0 go.opentelemetry.io/collector/confmap v1.19.0 go.opentelemetry.io/collector/confmap/provider/envprovider v1.19.0 @@ -712,3 +713,5 @@ require ( ) replace github.com/solarwinds/solarwinds-otel-collector/exporter/solarwindsexporter => ../../exporter/solarwindsexporter + +replace github.com/solarwinds/solarwinds-otel-collector/extension/solarwindsextension => ../../extension/solarwindsextension diff --git a/exporter/solarwindsexporter/README.md b/exporter/solarwindsexporter/README.md index 553db01..b8886ef 100644 --- a/exporter/solarwindsexporter/README.md +++ b/exporter/solarwindsexporter/README.md @@ -13,16 +13,12 @@ SolarWinds Exporter is a convenience wrapper around [OTLP gRPC Exporter](https:/ ## Getting Started -You just need to include the SolarWinds Exporter in your exporter definitions and provide the following minimal configuration: +You just need to include the SolarWinds Exporter in your exporter definitions and no additional configuration is needed. It needs to be used together with the Solarwinds Extension. ```yaml exporters: solarwinds: - token: "YOUR-INGESTION-TOKEN" - data_center: "na-01" ``` -- `token` (mandatory) - You can generate your token in your SolarWinds Observability SaaS account under _Settings / API Tokens / Create API Token_. The type is "Ingestion". You can find the complete documentation [here](https://documentation.solarwinds.com/en/success_center/observability/content/settings/api-tokens.htm). -- `data_center` (mandatory) - Data center is the region you picked during the sign-up process. You can easily see in URLs after logging in to SolarWinds Observability SaaS - it's either `na-01`, `na-02` or `eu-01`. Please refer to the [documentation](https://documentation.solarwinds.com/en/success_center/observability/content/system_requirements/endpoints.htm#Find) for details. ## Full configuration diff --git a/exporter/solarwindsexporter/config.go b/exporter/solarwindsexporter/config.go index 3ca634f..6ec64bd 100644 --- a/exporter/solarwindsexporter/config.go +++ b/exporter/solarwindsexporter/config.go @@ -15,9 +15,7 @@ package solarwindsexporter import ( - "errors" "fmt" - "strings" "time" "go.opentelemetry.io/collector/component" @@ -29,37 +27,11 @@ import ( "go.opentelemetry.io/collector/exporter/otlpexporter" ) -// dataCenterToURLMapping maps a data center ID to -// to its corresponding OTLP endpoint URL. -var dataCenterToURLMapping = map[string]string{ - "na-01": "otel.collector.na-01.cloud.solarwinds.com:443", - "na-02": "otel.collector.na-02.cloud.solarwinds.com:443", - "eu-01": "otel.collector.eu-01.cloud.solarwinds.com:443", -} - -// lookupDataCenterURL returns the OTLP endpoint URL -// for a `dc` data center ID. Matching is case-insensitive. -// It fails with an error if `dc` doesn't identify a data center. -func lookupDataCenterURL(dc string) (string, error) { - dcLowercase := strings.ToLower(dc) - - url, ok := dataCenterToURLMapping[dcLowercase] - if !ok { - return "", fmt.Errorf("unknown data center ID: %s", dc) - } - - return url, nil -} - // Config represents a Solarwinds Exporter configuration. type Config struct { - // DataCenter ID (e.g. na-01). - DataCenter string `mapstructure:"data_center"` - // EndpointURLOverride sets OTLP endpoint directly. - // Warning: Intended for testing use only, use `DataCenter` instead. - EndpointURLOverride string `mapstructure:"endpoint_url_override"` - // IngestionToken is your secret generated SWO ingestion token. - IngestionToken configopaque.String `mapstructure:"token"` + // ExtensionName identifies a Solarwinds Extension to + // use for obtaining connection credentials in this exporter. + ExtensionName string `mapstructure:"extension_name"` // BackoffSettings configures retry behavior of the exporter. // See [configretry.BackOffConfig] documentation. BackoffSettings configretry.BackOffConfig `mapstructure:"retry_on_failure"` @@ -68,12 +40,13 @@ type Config struct { QueueSettings exporterhelper.QueueConfig `mapstructure:"sending_queue"` // Timeout configures timeout in the underlying OTLP exporter. Timeout exporterhelper.TimeoutConfig `mapstructure:"timeout,squash"` + // ingestionToken stores the token provided by the Solarwinds Extension. + ingestionToken configopaque.String `mapstructure:"-"` + // endpointURL stores the URL provided by the Solarwinds Extension. + endpointURL string `mapstructure:"-"` } // NewDefaultConfig creates a new default configuration. -// -// Warning: it doesn't define mandatory `Token` and `DataCenter` -// fields that need to be explicitly provided. func NewDefaultConfig() component.Config { // Using a higher default than OTLP Exporter does (5s) // based on previous experience with unnecessary timeouts. @@ -90,18 +63,6 @@ func NewDefaultConfig() component.Config { // Validate checks the configuration for its validity. func (cfg *Config) Validate() error { - if cfg.DataCenter == "" && cfg.EndpointURLOverride == "" { - return errors.New("invalid configuration: data center must be provided") - } - - if _, err := lookupDataCenterURL(cfg.DataCenter); err != nil { - return fmt.Errorf("invalid data center ID: %w", err) - } - - if cfg.IngestionToken == "" { - return errors.New("invalid configuration: token must be set") - } - return nil } @@ -111,15 +72,8 @@ func (cfg *Config) OTLPConfig() (*otlpexporter.Config, error) { return nil, err } - // Use overridden URL if provided. - endpointURL := cfg.EndpointURLOverride - if endpointURL == "" { - // Error doesn't need to be checked, it's been validated above. - endpointURL, _ = lookupDataCenterURL(cfg.DataCenter) - } - // Headers - set bearer auth. - bearer := fmt.Sprintf("Bearer %s", string(cfg.IngestionToken)) + bearer := fmt.Sprintf("Bearer %s", string(cfg.ingestionToken)) headers := map[string]configopaque.String{ "Authorization": configopaque.String(bearer), } @@ -130,7 +84,7 @@ func (cfg *Config) OTLPConfig() (*otlpexporter.Config, error) { Keepalive: configgrpc.NewDefaultKeepaliveClientConfig(), BalancerName: configgrpc.BalancerName(), Headers: headers, - Endpoint: endpointURL, + Endpoint: cfg.endpointURL, } otlpConfig := &otlpexporter.Config{ diff --git a/exporter/solarwindsexporter/config_test.go b/exporter/solarwindsexporter/config_test.go index 37f7d9e..d9e072f 100644 --- a/exporter/solarwindsexporter/config_test.go +++ b/exporter/solarwindsexporter/config_test.go @@ -21,7 +21,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "go.opentelemetry.io/collector/config/configopaque" "go.opentelemetry.io/collector/config/configretry" "go.opentelemetry.io/collector/confmap" "go.opentelemetry.io/collector/confmap/confmaptest" @@ -52,9 +51,7 @@ func TestConfigUnmarshalFull(t *testing.T) { // Verify the values. assert.Equal(t, &Config{ - DataCenter: "na-01", - EndpointURLOverride: "127.0.0.1:1234", - IngestionToken: "TOKEN", + ExtensionName: "swo", BackoffSettings: configretry.BackOffConfig{ Enabled: false, InitialInterval: 15000000000, @@ -89,107 +86,15 @@ func TestConfigValidateOK(t *testing.T) { assert.NoError(t, cfg.(*Config).Validate()) } -// TestConfigValidateMissingToken verifies that -// the validation of a configuration file with -// the token missing fails as expected. -func TestConfigValidateMissingToken(t *testing.T) { - cfgFile := loadConfigTestdata(t, "missing_token") - - // Parse configuration. - factory := NewFactory() - cfg := factory.CreateDefaultConfig() - require.NoError(t, cfgFile.Unmarshal(&cfg)) - - assert.ErrorContains( - t, - cfg.(*Config).Validate(), - "invalid configuration: token must be set", - ) -} - -// TestConfigValidateMissingDataCenter verifies that -// the validation of a configuration file with -// the dataCenter ID missing fails as expected. -func TestConfigValidateMissingDataCenter(t *testing.T) { - cfgFile := loadConfigTestdata(t, "missing_dc") - - // Parse configuration. - factory := NewFactory() - cfg := factory.CreateDefaultConfig() - require.NoError(t, cfgFile.Unmarshal(&cfg)) - - assert.ErrorContains( - t, - cfg.(*Config).Validate(), - "invalid configuration: data center must be provided", - ) -} - -// TestConfigValidateDataCenters verifies mappings -// for data centers (the mapping is case-insensitive). -func TestConfigValidateDataCenters(t *testing.T) { - type test struct { - dataCenter string - url string - ok bool - } - - tests := []test{ - {dataCenter: "na-01", url: "otel.collector.na-01.cloud.solarwinds.com:443", ok: true}, - {dataCenter: "na-02", url: "otel.collector.na-02.cloud.solarwinds.com:443", ok: true}, - {dataCenter: "eu-01", url: "otel.collector.eu-01.cloud.solarwinds.com:443", ok: true}, - {dataCenter: "NA-01", url: "otel.collector.na-01.cloud.solarwinds.com:443", ok: true}, - {dataCenter: "apj-01", url: "", ok: false}, - } - - for _, tc := range tests { - // Try to find a dataCenter URL for its ID. - url, err := lookupDataCenterURL(tc.dataCenter) - - if tc.ok { // A URL should be returned. - require.NoError(t, err) - assert.Equal(t, tc.url, url) - } else { // It should fail. - assert.ErrorContains(t, err, "unknown data center ID") - } - } -} - // TestConfigTokenRedacted checks that the configuration // type doesn't leak its secret token unless it is accessed explicitly. func TestConfigTokenRedacted(t *testing.T) { cfg := &Config{ - DataCenter: "eu-01", - IngestionToken: "SECRET", + ingestionToken: "SECRET", } // This is the only way of accessing the actual token. - require.Equal(t, "SECRET", string(cfg.IngestionToken)) + require.Equal(t, "SECRET", string(cfg.ingestionToken)) // It is redacted when printed. - assert.Equal(t, "[REDACTED]", cfg.IngestionToken.String()) -} - -// TestConfigOTLPWithOverride converts exporter configuration to -// OTLP gRPC Exporter configuration and verifies that overridden -// endpoint and token propagate correctly. -func TestConfigOTLPWithOverride(t *testing.T) { - cfgFile := loadConfigTestdata(t, "url_override") - - // Parse configuration. - factory := NewFactory() - cfg := factory.CreateDefaultConfig() - require.NoError(t, cfgFile.Unmarshal(&cfg)) - - // Convert it to the OTLP Exporter configuration. - otlpCfg, err := cfg.(*Config).OTLPConfig() - require.NoError(t, err) - - // Verify that both the token and overridden URL were propagated - // to the OTLP configuration. - assert.Equal(t, "127.0.0.1:1234", otlpCfg.Endpoint) - assert.Equal( - t, - map[string]configopaque.String{"Authorization": "Bearer YOUR-INGESTION-TOKEN"}, - otlpCfg.Headers, - ) + assert.Equal(t, "[REDACTED]", cfg.ingestionToken.String()) } diff --git a/exporter/solarwindsexporter/factory.go b/exporter/solarwindsexporter/factory.go index 0cae0e3..e0dbd35 100644 --- a/exporter/solarwindsexporter/factory.go +++ b/exporter/solarwindsexporter/factory.go @@ -45,7 +45,10 @@ func createMetricsExporter( return nil, fmt.Errorf("unexpected config type: %T", cfg) } - metricsExporter := newExporter(ctx, exporterCfg, settings, metricsExporterType) + metricsExporter, err := newExporter(exporterCfg, settings, metricsExporterType) + if err != nil { + return nil, err // TODO: Wrap. + } return exporterhelper.NewMetrics( ctx, @@ -70,7 +73,11 @@ func createLogsExporter( return nil, fmt.Errorf("unexpected config type: %T", cfg) } - logsExporter := newExporter(ctx, exporterCfg, settings, logsExporterType) + logsExporter, err := newExporter(exporterCfg, settings, logsExporterType) + if err != nil { + return nil, err + } + return exporterhelper.NewLogs( ctx, settings, @@ -92,7 +99,10 @@ func createTracesExporter(ctx context.Context, return nil, fmt.Errorf("unexpected config type: %T", cfg) } - tracesExporter := newExporter(ctx, exporterCfg, settings, tracesExporterType) + tracesExporter, err := newExporter(exporterCfg, settings, tracesExporterType) + if err != nil { + return nil, err + } return exporterhelper.NewTraces( ctx, diff --git a/exporter/solarwindsexporter/generated_component_test.go b/exporter/solarwindsexporter/generated_component_test.go index 1fa055f..3986db9 100644 --- a/exporter/solarwindsexporter/generated_component_test.go +++ b/exporter/solarwindsexporter/generated_component_test.go @@ -1,17 +1,3 @@ -// Copyright 2024 SolarWinds Worldwide, LLC. All rights reserved. -// -// 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. - // Code generated by mdatagen. DO NOT EDIT. package solarwindsexporter diff --git a/exporter/solarwindsexporter/generated_package_test.go b/exporter/solarwindsexporter/generated_package_test.go index 9d890da..8bb93fb 100644 --- a/exporter/solarwindsexporter/generated_package_test.go +++ b/exporter/solarwindsexporter/generated_package_test.go @@ -1,17 +1,3 @@ -// Copyright 2024 SolarWinds Worldwide, LLC. All rights reserved. -// -// 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. - // Code generated by mdatagen. DO NOT EDIT. package solarwindsexporter diff --git a/exporter/solarwindsexporter/internal/metadata/generated_status.go b/exporter/solarwindsexporter/internal/metadata/generated_status.go index 30617e7..2147cab 100644 --- a/exporter/solarwindsexporter/internal/metadata/generated_status.go +++ b/exporter/solarwindsexporter/internal/metadata/generated_status.go @@ -1,17 +1,3 @@ -// Copyright 2024 SolarWinds Worldwide, LLC. All rights reserved. -// -// 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. - // Code generated by mdatagen. DO NOT EDIT. package metadata diff --git a/exporter/solarwindsexporter/metadata.yaml b/exporter/solarwindsexporter/metadata.yaml index fa53e04..af63ca6 100644 --- a/exporter/solarwindsexporter/metadata.yaml +++ b/exporter/solarwindsexporter/metadata.yaml @@ -6,7 +6,3 @@ status: stability: development: [traces, metrics, logs] -tests: - config: - data_center: "na-01" - token: "THIS-IS-A-TEST" diff --git a/exporter/solarwindsexporter/solarwinds_exporter.go b/exporter/solarwindsexporter/solarwinds_exporter.go index 7f9a744..9bee7df 100644 --- a/exporter/solarwindsexporter/solarwinds_exporter.go +++ b/exporter/solarwindsexporter/solarwinds_exporter.go @@ -16,9 +16,11 @@ package solarwindsexporter import ( "context" + "errors" "fmt" "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configopaque" "go.opentelemetry.io/collector/exporter" "go.opentelemetry.io/collector/exporter/otlpexporter" "go.opentelemetry.io/collector/pdata/plog" @@ -34,58 +36,80 @@ const ( tracesExporterType ) +var extensionType = component.MustNewType("solarwinds") + type solarwindsExporter struct { exporterType config *Config - settings component.TelemetrySettings + settings exporter.Settings metrics exporter.Metrics logs exporter.Logs traces exporter.Traces } func newExporter( - ctx context.Context, cfg *Config, settings exporter.Settings, typ exporterType, -) *solarwindsExporter { +) (*solarwindsExporter, error) { if err := cfg.Validate(); err != nil { - panic(err) + return nil, fmt.Errorf("validation of configuration failed: %w", err) } swiExporter := &solarwindsExporter{ - config: cfg, - settings: settings.TelemetrySettings, - } - if err := swiExporter.initExporterType(ctx, settings, typ); err != nil { - panic(err) + exporterType: typ, + config: cfg, + settings: settings, } - return swiExporter + return swiExporter, nil } -func (e *solarwindsExporter) initExporterType( +func (swiExporter *solarwindsExporter) initExporterType( ctx context.Context, settings exporter.Settings, + host component.Host, typ exporterType, ) error { - e.exporterType = typ + swiExporter.exporterType = typ + var extensionID *component.ID + if swiExporter.config.ExtensionName != "" { + id := component.NewIDWithName(extensionType, swiExporter.config.ExtensionName) + extensionID = &id + } + + swiExtension := findExtension(host.GetExtensions(), extensionID) + if swiExtension == nil { + return errors.New("solarwinds extension not found") + } + + // Get token from the extensions. + token := swiExtension.Token() + swiExporter.config.ingestionToken = token + + // Get URl from the extension. + url, err := swiExtension.Url() + if err != nil { + return fmt.Errorf(": %w", err) + } + swiExporter.config.endpointURL = url + otlpExporter := otlpexporter.NewFactory() - otlpCfg, err := e.config.OTLPConfig() + otlpCfg, err := swiExporter.config.OTLPConfig() if err != nil { return err } switch typ { case metricsExporterType: - e.metrics, err = otlpExporter.CreateMetrics(ctx, settings, otlpCfg) + swiExporter.metrics, err = otlpExporter.CreateMetrics(ctx, settings, otlpCfg) return err case logsExporterType: - e.logs, err = otlpExporter.CreateLogs(ctx, settings, otlpCfg) + swiExporter.logs, err = otlpExporter.CreateLogs(ctx, settings, otlpCfg) return err case tracesExporterType: - e.traces, err = otlpExporter.CreateTraces(ctx, settings, otlpCfg) + swiExporter.traces, err = otlpExporter.CreateTraces(ctx, settings, otlpCfg) return err default: return fmt.Errorf("unknown exporter type: %v", typ) @@ -93,52 +117,85 @@ func (e *solarwindsExporter) initExporterType( } -func (e *solarwindsExporter) start(ctx context.Context, host component.Host) error { - switch e.exporterType { +type EndpointConfigProvider interface { + Url() (string, error) + Token() configopaque.String +} + +func findExtension(extensions map[component.ID]component.Component, cfgExtensionID *component.ID) EndpointConfigProvider { + foundExtensions := make([]EndpointConfigProvider, 0) + + for foundExtensionID, ext := range extensions { + if swiExtension, ok := ext.(EndpointConfigProvider); ok { + // If configured extension ID is found, return it. + if cfgExtensionID != nil && *cfgExtensionID == foundExtensionID { + return swiExtension + } + + // Otherwise, store it to the slice. + foundExtensions = append(foundExtensions, swiExtension) + } + } + + // If no extension name configured and there is only one + // found matching the type, return it. + if len(foundExtensions) == 1 && cfgExtensionID != nil { + return foundExtensions[0] + } + + return nil +} + +func (swiExporter *solarwindsExporter) start(ctx context.Context, host component.Host) error { + if err := swiExporter.initExporterType(ctx, swiExporter.settings, host, swiExporter.exporterType); err != nil { + return fmt.Errorf("failed to initialiaze exporter: %w", err) + } + + switch swiExporter.exporterType { case metricsExporterType: - return e.metrics.Start(ctx, host) + return swiExporter.metrics.Start(ctx, host) case logsExporterType: - return e.logs.Start(ctx, host) + return swiExporter.logs.Start(ctx, host) case tracesExporterType: - return e.traces.Start(ctx, host) + return swiExporter.traces.Start(ctx, host) default: - return fmt.Errorf("unknown exporter type: %v", e.exporterType) + return fmt.Errorf("unknown exporter type: %v", swiExporter.exporterType) } } -func (e *solarwindsExporter) shutdown(ctx context.Context) error { - switch e.exporterType { +func (swiExporter *solarwindsExporter) shutdown(ctx context.Context) error { + switch swiExporter.exporterType { case metricsExporterType: - return e.metrics.Shutdown(ctx) + return swiExporter.metrics.Shutdown(ctx) case logsExporterType: - return e.logs.Shutdown(ctx) + return swiExporter.logs.Shutdown(ctx) case tracesExporterType: - return e.traces.Shutdown(ctx) + return swiExporter.traces.Shutdown(ctx) default: - return fmt.Errorf("unknown exporter type: %v", e.exporterType) + return fmt.Errorf("unknown exporter type: %v", swiExporter.exporterType) } } -func (e *solarwindsExporter) pushMetrics(ctx context.Context, metrics pmetric.Metrics) error { +func (swiExporter *solarwindsExporter) pushMetrics(ctx context.Context, metrics pmetric.Metrics) error { if metrics.MetricCount() == 0 { return nil } - return e.metrics.ConsumeMetrics(ctx, metrics) + return swiExporter.metrics.ConsumeMetrics(ctx, metrics) } -func (e *solarwindsExporter) pushLogs(ctx context.Context, logs plog.Logs) error { +func (swiExporter *solarwindsExporter) pushLogs(ctx context.Context, logs plog.Logs) error { if logs.LogRecordCount() == 0 { return nil } - return e.logs.ConsumeLogs(ctx, logs) + return swiExporter.logs.ConsumeLogs(ctx, logs) } -func (e *solarwindsExporter) pushTraces(ctx context.Context, traces ptrace.Traces) error { +func (swiExporter *solarwindsExporter) pushTraces(ctx context.Context, traces ptrace.Traces) error { if traces.SpanCount() == 0 { return nil } - return e.traces.ConsumeTraces(ctx, traces) + return swiExporter.traces.ConsumeTraces(ctx, traces) } diff --git a/exporter/solarwindsexporter/testdata/full.yaml b/exporter/solarwindsexporter/testdata/full.yaml index 78428fe..e0b7018 100644 --- a/exporter/solarwindsexporter/testdata/full.yaml +++ b/exporter/solarwindsexporter/testdata/full.yaml @@ -1,6 +1,4 @@ -token: "TOKEN" -data_center: "na-01" -endpoint_url_override: "127.0.0.1:1234" +extension_name: "swo" timeout: "20s" sending_queue: enabled: true diff --git a/exporter/solarwindsexporter/testdata/minimal.yaml b/exporter/solarwindsexporter/testdata/minimal.yaml index 762db58..8b13789 100644 --- a/exporter/solarwindsexporter/testdata/minimal.yaml +++ b/exporter/solarwindsexporter/testdata/minimal.yaml @@ -1,2 +1 @@ -token: "YOUR-INGESTION-TOKEN" -data_center: "na-01" + diff --git a/exporter/solarwindsexporter/testdata/missing_dc.yaml b/exporter/solarwindsexporter/testdata/missing_dc.yaml deleted file mode 100644 index 8b4fe9e..0000000 --- a/exporter/solarwindsexporter/testdata/missing_dc.yaml +++ /dev/null @@ -1 +0,0 @@ -token: "YOUR-INGESTION-TOKEN" diff --git a/exporter/solarwindsexporter/testdata/missing_token.yaml b/exporter/solarwindsexporter/testdata/missing_token.yaml deleted file mode 100644 index 46d8215..0000000 --- a/exporter/solarwindsexporter/testdata/missing_token.yaml +++ /dev/null @@ -1 +0,0 @@ -data_center: "na-01" diff --git a/exporter/solarwindsexporter/testdata/url_override.yaml b/exporter/solarwindsexporter/testdata/url_override.yaml deleted file mode 100644 index ef329f6..0000000 --- a/exporter/solarwindsexporter/testdata/url_override.yaml +++ /dev/null @@ -1,4 +0,0 @@ -token: "YOUR-INGESTION-TOKEN" -data_center: "na-01" -endpoint_url_override: "127.0.0.1:1234" - diff --git a/extension/solarwindsextension/Makefile b/extension/solarwindsextension/Makefile new file mode 100644 index 0000000..60d73c6 --- /dev/null +++ b/extension/solarwindsextension/Makefile @@ -0,0 +1,7 @@ +.PHONY: test +test: + go test ./... + +.PHONY: generate +generate: + go generate ./... diff --git a/extension/solarwindsextension/README.md b/extension/solarwindsextension/README.md new file mode 100644 index 0000000..10cfd17 --- /dev/null +++ b/extension/solarwindsextension/README.md @@ -0,0 +1,11 @@ +# Solarwinds Extension + + +| Status | | +| ------------- |-----------| +| Stability | [development] | +| Distributions | [] | +| Issues | [![Open issues](https://img.shields.io/github/issues-search/solarwinds/solarwinds-otel-collector?query=is%3Aissue%20is%3Aopen%20label%3Aextension%2Fsolarwinds%20&label=open&color=orange&logo=opentelemetry)](https://github.com/solarwinds/solarwinds-otel-collector/issues?q=is%3Aopen+is%3Aissue+label%3Aextension%2Fsolarwinds) [![Closed issues](https://img.shields.io/github/issues-search/solarwinds/solarwinds-otel-collector?query=is%3Aissue%20is%3Aclosed%20label%3Aextension%2Fsolarwinds%20&label=closed&color=blue&logo=opentelemetry)](https://github.com/solarwinds/solarwinds-otel-collector/issues?q=is%3Aclosed+is%3Aissue+label%3Aextension%2Fsolarwinds) | + +[development]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#development + diff --git a/extension/solarwindsextension/doc.go b/extension/solarwindsextension/doc.go new file mode 100644 index 0000000..159dd9a --- /dev/null +++ b/extension/solarwindsextension/doc.go @@ -0,0 +1,2 @@ +//go:generate mdatagen metadata.yaml +package solarwindsextension diff --git a/extension/solarwindsextension/endpoint_config.go b/extension/solarwindsextension/endpoint_config.go new file mode 100644 index 0000000..27fba74 --- /dev/null +++ b/extension/solarwindsextension/endpoint_config.go @@ -0,0 +1,27 @@ +package solarwindsextension + +import ( + "github.com/solarwinds/solarwinds-otel-collector/extension/solarwindsextension/internal" + "go.opentelemetry.io/collector/config/configopaque" +) + +type EndpointConfig interface { + Url() (string, error) + Token() configopaque.String +} + +type endpointConfig struct{ cfg *internal.Config } + +var _ EndpointConfig = (*endpointConfig)(nil) + +func newEndpointConfig(cfg *internal.Config) *endpointConfig { + return &endpointConfig{cfg: cfg} +} + +func (c *endpointConfig) Url() (string, error) { + return c.cfg.EndpointUrl() +} + +func (c *endpointConfig) Token() configopaque.String { + return c.cfg.IngestionToken +} diff --git a/extension/solarwindsextension/extension.go b/extension/solarwindsextension/extension.go new file mode 100644 index 0000000..afcf198 --- /dev/null +++ b/extension/solarwindsextension/extension.go @@ -0,0 +1,64 @@ +package solarwindsextension + +import ( + "context" + "errors" + "github.com/solarwinds/solarwinds-otel-collector/extension/solarwindsextension/internal" + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/extension" + "go.opentelemetry.io/collector/pdata/pcommon" + "go.uber.org/zap" +) + +type SolarwindsExtension struct { + logger *zap.Logger + config *internal.Config + uptimeMetric *internal.UptimeMetric + heartbeat *internal.Heartbeat + exporter *internal.Exporter +} + +func newExtension(ctx context.Context, set extension.Settings, cfg *internal.Config) (*SolarwindsExtension, error) { + set.Logger.Info("Creating SolarwindsExtension") + set.Logger.Info("Config", zap.Any("config", cfg)) + e := &SolarwindsExtension{ + logger: set.Logger, + config: cfg, + } + var err error + e.exporter, err = internal.NewExporter(ctx, set, cfg, e.decorateResourceAttributes) + if err != nil { + return nil, err + } + + e.uptimeMetric = internal.NewUptimeMetric(set.Logger) + e.heartbeat = internal.NewHeartbeat(e.logger, e.exporter.PushMetrics, e.uptimeMetric.AddUptimeMetric) + + return e, nil +} + +func (e *SolarwindsExtension) GetEndpointConfig() EndpointConfig { return newEndpointConfig(e.config) } + +func (e *SolarwindsExtension) Start(ctx context.Context, host component.Host) error { + e.logger.Info("Starting SolarwindsExtension") + err := e.exporter.Start(ctx, host) + if err != nil { + return err + } + return e.heartbeat.Start() +} + +func (e *SolarwindsExtension) Shutdown(ctx context.Context) error { + e.logger.Info("Shutting down SolarwindsExtension") + // Everything must be shut down, regardless of the failure. + return errors.Join( + e.heartbeat.Shutdown(), + e.exporter.Shutdown(ctx)) +} + +func (e *SolarwindsExtension) decorateResourceAttributes(resource pcommon.Resource) error { + if e.config.CollectorName != "" { + resource.Attributes().PutStr("collector_name", e.config.CollectorName) + } + return nil +} diff --git a/extension/solarwindsextension/factory.go b/extension/solarwindsextension/factory.go new file mode 100644 index 0000000..8c0aa49 --- /dev/null +++ b/extension/solarwindsextension/factory.go @@ -0,0 +1,26 @@ +package solarwindsextension + +import ( + "context" + "fmt" + "github.com/solarwinds/solarwinds-otel-collector/extension/solarwindsextension/internal" + "github.com/solarwinds/solarwinds-otel-collector/extension/solarwindsextension/internal/metadata" + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/extension" +) + +func NewFactory() extension.Factory { + return extension.NewFactory( + metadata.Type, + internal.NewDefaultConfig, + createExtension, + metadata.ExtensionStability) +} + +func createExtension(ctx context.Context, set extension.Settings, cfg component.Config) (extension.Extension, error) { + extCfg, ok := cfg.(*internal.Config) + if !ok { + return nil, fmt.Errorf("unexpected config type: %T", cfg) + } + return newExtension(ctx, set, extCfg) +} diff --git a/extension/solarwindsextension/generated_component_test.go b/extension/solarwindsextension/generated_component_test.go new file mode 100644 index 0000000..f37af2b --- /dev/null +++ b/extension/solarwindsextension/generated_component_test.go @@ -0,0 +1,49 @@ +// Code generated by mdatagen. DO NOT EDIT. + +package solarwindsextension + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/confmap/confmaptest" + "go.opentelemetry.io/collector/extension/extensiontest" +) + +func TestComponentFactoryType(t *testing.T) { + require.Equal(t, "solarwinds", NewFactory().Type().String()) +} + +func TestComponentConfigStruct(t *testing.T) { + require.NoError(t, componenttest.CheckConfigStruct(NewFactory().CreateDefaultConfig())) +} + +func TestComponentLifecycle(t *testing.T) { + factory := NewFactory() + + cm, err := confmaptest.LoadConf("metadata.yaml") + require.NoError(t, err) + cfg := factory.CreateDefaultConfig() + sub, err := cm.Sub("tests::config") + require.NoError(t, err) + require.NoError(t, sub.Unmarshal(&cfg)) + t.Run("shutdown", func(t *testing.T) { + e, err := factory.Create(context.Background(), extensiontest.NewNopSettings(), cfg) + require.NoError(t, err) + err = e.Shutdown(context.Background()) + require.NoError(t, err) + }) + t.Run("lifecycle", func(t *testing.T) { + firstExt, err := factory.Create(context.Background(), extensiontest.NewNopSettings(), cfg) + require.NoError(t, err) + require.NoError(t, firstExt.Start(context.Background(), componenttest.NewNopHost())) + require.NoError(t, firstExt.Shutdown(context.Background())) + + secondExt, err := factory.Create(context.Background(), extensiontest.NewNopSettings(), cfg) + require.NoError(t, err) + require.NoError(t, secondExt.Start(context.Background(), componenttest.NewNopHost())) + require.NoError(t, secondExt.Shutdown(context.Background())) + }) +} diff --git a/extension/solarwindsextension/generated_package_test.go b/extension/solarwindsextension/generated_package_test.go new file mode 100644 index 0000000..61bd5ee --- /dev/null +++ b/extension/solarwindsextension/generated_package_test.go @@ -0,0 +1,12 @@ +// Code generated by mdatagen. DO NOT EDIT. + +package solarwindsextension + +import ( + "go.uber.org/goleak" + "testing" +) + +func TestMain(m *testing.M) { + goleak.VerifyTestMain(m) +} diff --git a/extension/solarwindsextension/go.mod b/extension/solarwindsextension/go.mod new file mode 100644 index 0000000..26e0371 --- /dev/null +++ b/extension/solarwindsextension/go.mod @@ -0,0 +1,74 @@ +module github.com/solarwinds/solarwinds-otel-collector/extension/solarwindsextension + +go 1.22.7 + +require ( + github.com/stretchr/testify v1.9.0 + go.opentelemetry.io/collector/component v0.113.0 + go.opentelemetry.io/collector/config/configgrpc v0.113.0 + go.opentelemetry.io/collector/config/configopaque v1.19.0 + go.opentelemetry.io/collector/config/configtls v1.19.0 + go.opentelemetry.io/collector/confmap v1.19.0 + go.opentelemetry.io/collector/exporter v0.113.0 + go.opentelemetry.io/collector/exporter/otlpexporter v0.113.0 + go.opentelemetry.io/collector/extension v0.113.0 + go.opentelemetry.io/collector/pdata v1.19.0 + go.uber.org/goleak v1.3.0 + go.uber.org/zap v1.27.0 +) + +require ( + github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/fsnotify/fsnotify v1.8.0 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-viper/mapstructure/v2 v2.2.1 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/snappy v0.0.4 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/compress v1.17.11 // indirect + github.com/knadh/koanf/maps v0.1.1 // indirect + github.com/knadh/koanf/providers/confmap v0.1.0 // indirect + github.com/knadh/koanf/v2 v2.1.1 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/mostynb/go-grpc-compression v1.2.3 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/rogpeppe/go-internal v1.13.1 // indirect + go.opentelemetry.io/collector/client v1.19.0 // indirect + go.opentelemetry.io/collector/config/configauth v0.113.0 // indirect + go.opentelemetry.io/collector/config/configcompression v1.19.0 // indirect + go.opentelemetry.io/collector/config/confignet v1.19.0 // indirect + go.opentelemetry.io/collector/config/configretry v1.19.0 // indirect + go.opentelemetry.io/collector/config/configtelemetry v0.113.0 // indirect + go.opentelemetry.io/collector/config/internal v0.113.0 // indirect + go.opentelemetry.io/collector/consumer v0.113.0 // indirect + go.opentelemetry.io/collector/consumer/consumererror v0.113.0 // indirect + go.opentelemetry.io/collector/consumer/consumererror/consumererrorprofiles v0.113.0 // indirect + go.opentelemetry.io/collector/consumer/consumerprofiles v0.113.0 // indirect + go.opentelemetry.io/collector/exporter/exporterhelper/exporterhelperprofiles v0.113.0 // indirect + go.opentelemetry.io/collector/exporter/exporterprofiles v0.113.0 // indirect + go.opentelemetry.io/collector/extension/auth v0.113.0 // indirect + go.opentelemetry.io/collector/extension/experimental/storage v0.113.0 // indirect + go.opentelemetry.io/collector/pdata/pprofile v0.113.0 // indirect + go.opentelemetry.io/collector/pipeline v0.113.0 // indirect + go.opentelemetry.io/collector/pipeline/pipelineprofiles v0.113.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.56.0 // indirect + go.opentelemetry.io/otel v1.31.0 // indirect + go.opentelemetry.io/otel/metric v1.31.0 // indirect + go.opentelemetry.io/otel/sdk v1.31.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.31.0 // indirect + go.opentelemetry.io/otel/trace v1.31.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/net v0.30.0 // indirect + golang.org/x/sys v0.26.0 // indirect + golang.org/x/text v0.19.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 // indirect + google.golang.org/grpc v1.67.1 // indirect + google.golang.org/protobuf v1.35.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/extension/solarwindsextension/go.sum b/extension/solarwindsextension/go.sum new file mode 100644 index 0000000..adf8e14 --- /dev/null +++ b/extension/solarwindsextension/go.sum @@ -0,0 +1,188 @@ +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= +github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= +github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= +github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= +github.com/knadh/koanf/maps v0.1.1 h1:G5TjmUh2D7G2YWf5SQQqSiHRJEjaicvU0KpypqB3NIs= +github.com/knadh/koanf/maps v0.1.1/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI= +github.com/knadh/koanf/providers/confmap v0.1.0 h1:gOkxhHkemwG4LezxxN8DMOFopOPghxRVp7JbIvdvqzU= +github.com/knadh/koanf/providers/confmap v0.1.0/go.mod h1:2uLhxQzJnyHKfxG927awZC7+fyHFdQkd697K4MdLnIU= +github.com/knadh/koanf/v2 v2.1.1 h1:/R8eXqasSTsmDCsAyYj+81Wteg8AqrV9CP6gvsTsOmM= +github.com/knadh/koanf/v2 v2.1.1/go.mod h1:4mnTRbZCK+ALuBXHZMjDfG9y714L7TykVnZkXbMU3Es= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/mostynb/go-grpc-compression v1.2.3 h1:42/BKWMy0KEJGSdWvzqIyOZ95YcR9mLPqKctH7Uo//I= +github.com/mostynb/go-grpc-compression v1.2.3/go.mod h1:AghIxF3P57umzqM9yz795+y1Vjs47Km/Y2FE6ouQ7Lg= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.opentelemetry.io/collector v0.113.0 h1:dBuo2/OKBhoMCR86W4fFJLXGQ0gJfKRmi65AZwFkU2I= +go.opentelemetry.io/collector v0.113.0/go.mod h1:XbjD4Yw9LunLo3IJu3ZZytNZ0drEVznxw1Z14Ujlw3s= +go.opentelemetry.io/collector/client v1.19.0 h1:TUal8WV1agTrZStgE7BJ8ZC0IHLGtrfgO9ogU9t1mv8= +go.opentelemetry.io/collector/client v1.19.0/go.mod h1:jgiXMEM6l8L2QEyf2I/M47Zd8+G7e4z+6H8q5SkHOlQ= +go.opentelemetry.io/collector/component v0.113.0 h1:/nx+RvZgxUEXP+YcTj69rEtuSEGkfaCyp/ad5zQGLjU= +go.opentelemetry.io/collector/component v0.113.0/go.mod h1:2T779hIGHU9i7xbXbV3q1/JnRw2FyzUYXW2vq47A6EU= +go.opentelemetry.io/collector/config/configauth v0.113.0 h1:CBz43fGpN41MwLdwe3mw/XVSIDvGRMT8aaaPuqKukTU= +go.opentelemetry.io/collector/config/configauth v0.113.0/go.mod h1:Q8SlxrIvL3FJO51hXa4n9ARvox04lK8mmpjf4b3UNAU= +go.opentelemetry.io/collector/config/configcompression v1.19.0 h1:bTSjTLhnPXX1NSFM6GzguEM/NBe8QUPsXHc9kMOAJzE= +go.opentelemetry.io/collector/config/configcompression v1.19.0/go.mod h1:pnxkFCLUZLKWzYJvfSwZnPrnm0twX14CYj2ADth5xiU= +go.opentelemetry.io/collector/config/configgrpc v0.113.0 h1:rNbRd033JlIeU+TH+3bEt4OwRlEwrktWdf6V+VUJUPk= +go.opentelemetry.io/collector/config/configgrpc v0.113.0/go.mod h1:InXxPUj1oxJ57Sl954d2tQxXTgVHhfppFYjMwGjQukg= +go.opentelemetry.io/collector/config/confignet v1.19.0 h1:gEDTd8zLx4pPpG5///XPRpbYUpvKsuQzDdM5IEULY9w= +go.opentelemetry.io/collector/config/confignet v1.19.0/go.mod h1:o3v4joAEjvLwntqexg5ixMqRrU1+Vst+jWuCUaBNgOg= +go.opentelemetry.io/collector/config/configopaque v1.19.0 h1:7uvntQeAAtqCaeiS2dDGrT1wLPhWvDlEsD3SliA/koQ= +go.opentelemetry.io/collector/config/configopaque v1.19.0/go.mod h1:6zlLIyOoRpJJ+0bEKrlZOZon3rOp5Jrz9fMdR4twOS4= +go.opentelemetry.io/collector/config/configretry v1.19.0 h1:DEg8PXpo4ahMYgMzZZUU2cPcDF4vqowZlvimJ/t9InY= +go.opentelemetry.io/collector/config/configretry v1.19.0/go.mod h1:KvQF5cfphq1rQm1dKR4eLDNQYw6iI2fY72NMZVa+0N0= +go.opentelemetry.io/collector/config/configtelemetry v0.113.0 h1:hweTRrVddnUeA3k7HzRY4oUR9lRdMa7of3mHNUS5YyA= +go.opentelemetry.io/collector/config/configtelemetry v0.113.0/go.mod h1:R0MBUxjSMVMIhljuDHWIygzzJWQyZHXXWIgQNxcFwhc= +go.opentelemetry.io/collector/config/configtls v1.19.0 h1:GQ/cF1hgNqHVBq2oSSrOFXxVCyMDyd5kq4R/RMEbL98= +go.opentelemetry.io/collector/config/configtls v1.19.0/go.mod h1:1hyqnYB3JqEUlk1ME/s9HYz4oCRcxQCRxsJitFFT/cA= +go.opentelemetry.io/collector/config/internal v0.113.0 h1:9RAzH8v7ItFT1npHpvP0SvUzBHcZDliCGRo9Spp6v7c= +go.opentelemetry.io/collector/config/internal v0.113.0/go.mod h1:yC7E4h1Uj0SubxcFImh6OvBHFTjMh99+A5PuyIgDWqc= +go.opentelemetry.io/collector/confmap v1.19.0 h1:TQ0lZpAKqgsE0EKk+u4JA+uBbPYeFRmWP3GH43w40CY= +go.opentelemetry.io/collector/confmap v1.19.0/go.mod h1:GgNu1ElPGmLn9govqIfjaopvdspw4PJ9KeDtWC4E2Q4= +go.opentelemetry.io/collector/consumer v0.113.0 h1:KJSiK5vSIY9dgPxwKfQ3gOgKtQsqc+7IB7mGhUAL5c8= +go.opentelemetry.io/collector/consumer v0.113.0/go.mod h1:zHMlXYFaJlZoLCBR6UwWoyXZ/adcO1u2ydqUal3VmYU= +go.opentelemetry.io/collector/consumer/consumererror v0.113.0 h1:Hd2N7n9RKbnKRaVrdw6fPBoQko5zZIgCxwVxkL6SAIE= +go.opentelemetry.io/collector/consumer/consumererror v0.113.0/go.mod h1:o0MAGFdzcr7LFTUQ6iivPPhbVmn2ZVIYm3FPXk2+JUo= +go.opentelemetry.io/collector/consumer/consumererror/consumererrorprofiles v0.113.0 h1:2kLIt+6dGmhCd48CWXh3IEon/uW4+c8y81IGCA/h8wE= +go.opentelemetry.io/collector/consumer/consumererror/consumererrorprofiles v0.113.0/go.mod h1:/eESy7Ifyf7G6r6WUpEOq2tnfjIJ2QNB2EvZcEu0aWA= +go.opentelemetry.io/collector/consumer/consumerprofiles v0.113.0 h1:RftAcQUY5UOfbEK4s16jnORqTx16y9+PxA1lQwt98cQ= +go.opentelemetry.io/collector/consumer/consumerprofiles v0.113.0/go.mod h1:ZuHrQ4pWguh6dw0DgTfcUtdY/T+cnOJJNP6LMbm5Y5A= +go.opentelemetry.io/collector/consumer/consumertest v0.113.0 h1:ua2AjNx3DUA8qElXNkggB4w3VDL/rBKBvryOQkhumH8= +go.opentelemetry.io/collector/consumer/consumertest v0.113.0/go.mod h1:vK8o4ZTZSiG3rVyqxZcCNmT/cvEfx34ig7V65L9+6Rg= +go.opentelemetry.io/collector/exporter v0.113.0 h1:lDZJ6xfuhyLsT/7lqLhIN/ftA6G+9fuYFtubPFvNDxo= +go.opentelemetry.io/collector/exporter v0.113.0/go.mod h1:0W4NBf5NjWYxR8oJodmOybgN4O0MLazdJwwHevirvXg= +go.opentelemetry.io/collector/exporter/exporterhelper/exporterhelperprofiles v0.113.0 h1:Auz2vZYReIlyDvJ162OCO8XcV7L2BIbFb5HJWxerc5A= +go.opentelemetry.io/collector/exporter/exporterhelper/exporterhelperprofiles v0.113.0/go.mod h1:JQuawcAfDuzNneDF5Ep1CZJ5snsLp6Bh1gZcHhja7yU= +go.opentelemetry.io/collector/exporter/exporterprofiles v0.113.0 h1:8bsk3wYYNr+WAM5nZkFjiLYSTH9MsY2tm7nUpMWt3qc= +go.opentelemetry.io/collector/exporter/exporterprofiles v0.113.0/go.mod h1:/HFWF846XePYL/qKDtcEAFgkiGSkLUTaC59A5F48axM= +go.opentelemetry.io/collector/exporter/exportertest v0.113.0 h1:U6cRxjJS7td8iNriUI2QfEdH+Yj60ytyvpmnmKTw0+8= +go.opentelemetry.io/collector/exporter/exportertest v0.113.0/go.mod h1:SRz5jGyAjtNiWwJ93B1+Ndk1p3oFtQsyLw52UGeyRwc= +go.opentelemetry.io/collector/exporter/otlpexporter v0.113.0 h1://7diunG5SohqaYfqvHzCtcfrY7y3WQj0vklFYgeNW4= +go.opentelemetry.io/collector/exporter/otlpexporter v0.113.0/go.mod h1:THF0eq4lA6dYOho53iKFCBOv91HEeISZyep5dXr+fBU= +go.opentelemetry.io/collector/extension v0.113.0 h1:Vp/YSL8ZCkJQrP1lf2Bm5yaTvcp6ROO3AnfuSL3GEXM= +go.opentelemetry.io/collector/extension v0.113.0/go.mod h1:Pwp0TNqdHeER4V1I6H6oCvrto/riiOAqs3737BWCnjw= +go.opentelemetry.io/collector/extension/auth v0.113.0 h1:4ggRy1vepOabUiCWfU+6M9P/ftXojMUNAvBpeLihYj8= +go.opentelemetry.io/collector/extension/auth v0.113.0/go.mod h1:VbvAm2YZAqePkWgwn0m0vBaq3aC49CxPVwHmrJ24aeQ= +go.opentelemetry.io/collector/extension/experimental/storage v0.113.0 h1:Qq4IaB6bMUrf/bWoPZ5ESWywCt+vDi8I/ChYejIEPcc= +go.opentelemetry.io/collector/extension/experimental/storage v0.113.0/go.mod h1:BRmo+A7f06u/rhyLauU/Vogk+QRN0y1j2VVVgMGWrfQ= +go.opentelemetry.io/collector/pdata v1.19.0 h1:jmnU5R8TOCbwRr4B8sjdRxM7L5WnEKlQWX1dtLYxIbE= +go.opentelemetry.io/collector/pdata v1.19.0/go.mod h1:Ox1YVLe87cZDB/TL30i4SUz1cA5s6AM6SpFMfY61ICs= +go.opentelemetry.io/collector/pdata/pprofile v0.113.0 h1:VRf4p0VhfuaR+Epy/nMIlu/9t39WU9CUgHVUvpuGxfU= +go.opentelemetry.io/collector/pdata/pprofile v0.113.0/go.mod h1:5aDejksdXh5PdJN/OhpzATGT3kbNL0RMmw2Q0Q6E/o0= +go.opentelemetry.io/collector/pdata/testdata v0.113.0 h1:vRfn85jicO2F4eOTgsWtzmU/K3E/uZUtM1HEefvvJD8= +go.opentelemetry.io/collector/pdata/testdata v0.113.0/go.mod h1:sR+6eR+YEJhYZu9StbqzeWcCmHpfBAgX/qjP82HY9Gw= +go.opentelemetry.io/collector/pipeline v0.113.0 h1:vSRzRe3717jV0btCNPhVkhg2lu0uFxcm2VO+vhad/eE= +go.opentelemetry.io/collector/pipeline v0.113.0/go.mod h1:4vOvjVsoYTHVGTbfFwqfnQOSV2K3RKUHofh3jNRc2Mg= +go.opentelemetry.io/collector/pipeline/pipelineprofiles v0.113.0 h1:PwQnErsLvEd1x6VIyjLmKQot9huKWqIfEz1kd+8aj4k= +go.opentelemetry.io/collector/pipeline/pipelineprofiles v0.113.0/go.mod h1:tChJYsCG3wc6JPT9aJO3y+32V14NhmCFZOh3k5ORGdQ= +go.opentelemetry.io/collector/receiver v0.113.0 h1:vraAbkPy8Pz9x5X39gV+j9t6x23PNsY2aJ6gQMugRbQ= +go.opentelemetry.io/collector/receiver v0.113.0/go.mod h1:IUa8/lNw8Qh4L5Q3jOeRWKW0ebQPoNcfhytxN5Puq2A= +go.opentelemetry.io/collector/receiver/receiverprofiles v0.113.0 h1:uVxuzjGe2t1sbwahSBowVHYnGzpzn8brmfn8z1UHvQg= +go.opentelemetry.io/collector/receiver/receiverprofiles v0.113.0/go.mod h1:khKDkzYJR2x2OPUqGSmoSncdINT9lUE5IThiHPDbqZk= +go.opentelemetry.io/collector/receiver/receivertest v0.113.0 h1:0vOvz3S4Q/KwcNCS9C7zPo0uxD6RSWktG88yGdxfV6g= +go.opentelemetry.io/collector/receiver/receivertest v0.113.0/go.mod h1:sRq5ctm5UE/0Ar562wnCVQ1zbAie/D127D1WbtbEuEc= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.56.0 h1:yMkBS9yViCc7U7yeLzJPM2XizlfdVvBRSmsQDWu6qc0= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.56.0/go.mod h1:n8MR6/liuGB5EmTETUBeU5ZgqMOlqKRxUaqPQBOANZ8= +go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= +go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= +go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= +go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= +go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= +go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= +go.opentelemetry.io/otel/sdk/metric v1.31.0 h1:i9hxxLJF/9kkvfHppyLL55aW7iIJz4JjxTeYusH7zMc= +go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC0zgyxzW55lZzX43Y8= +go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= +go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= +golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 h1:QCqS/PdaHTSWGvupk2F/ehwHtGc0/GYkT+3GAcR1CCc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= +google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= +google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/extension/solarwindsextension/internal/config.go b/extension/solarwindsextension/internal/config.go new file mode 100644 index 0000000..cd64582 --- /dev/null +++ b/extension/solarwindsextension/internal/config.go @@ -0,0 +1,140 @@ +// Copyright 2024 SolarWinds Worldwide, LLC. All rights reserved. +// +// 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 internal + +import ( + "errors" + "fmt" + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/config/configgrpc" + "go.opentelemetry.io/collector/config/configopaque" + "go.opentelemetry.io/collector/config/configtls" + "go.opentelemetry.io/collector/exporter/otlpexporter" + "maps" + "slices" + "strings" +) + +// Config represents a Solarwinds Exporter configuration. +type Config struct { + // DataCenter ID (e.g. na-01). + DataCenter string `mapstructure:"data_center"` + // EndpointURLOverride sets OTLP endpoint directly. + // Warning: Intended for testing use only, use `DataCenter` instead. + EndpointURLOverride string `mapstructure:"endpoint_url_override"` + // IngestionToken is your secret generated SWO ingestion token. + IngestionToken configopaque.String `mapstructure:"token"` + // CollectorName is the name you will see in the SWO UI + CollectorName string `mapstructure:"collector_name"` +} + +var ( + missingDataCenterErr = errors.New("invalid configuration: 'data_center' must be set") + missingTokenErr = errors.New("invalid configuration: 'token' must be set") + missingCollectorNameErr = errors.New("invalid configuration: 'collector_name' must be set") +) + +// NewDefaultConfig creates a new default configuration. +// +// Warning: it doesn't define mandatory `Token` and `DataCenter` +// fields that need to be explicitly provided. +func NewDefaultConfig() component.Config { + return &Config{} +} + +// Validate checks the configuration for its validity. +func (cfg *Config) Validate() error { + if cfg.DataCenter == "" && cfg.EndpointURLOverride == "" { + return missingDataCenterErr + } + + if _, err := cfg.EndpointUrl(); err != nil { + return fmt.Errorf("invalid 'data_center' value: %w", err) + } + + if cfg.IngestionToken == "" { + return missingTokenErr + } + if cfg.CollectorName == "" { + return missingCollectorNameErr + } + + return nil +} + +// OTLPConfig generates a full OTLP Exporter configuration from the configuration. +func (cfg *Config) OTLPConfig() (*otlpexporter.Config, error) { + if err := cfg.Validate(); err != nil { + return nil, err + } + + endpointURL, err := cfg.EndpointUrl() + if err != nil { + return nil, err + } + + // Headers - set bearer auth. + bearer := configopaque.String(fmt.Sprintf("Bearer %s", string(cfg.IngestionToken))) + headers := map[string]configopaque.String{ + "Authorization": bearer, + } + + // gRPC client configuration. + otlpConfig := &otlpexporter.Config{ + ClientConfig: configgrpc.ClientConfig{ + TLSSetting: configtls.NewDefaultClientConfig(), + Keepalive: configgrpc.NewDefaultKeepaliveClientConfig(), + BalancerName: configgrpc.BalancerName(), + Headers: headers, + Endpoint: endpointURL, + }, + } + + if err = otlpConfig.Validate(); err != nil { + return nil, err + } + + return otlpConfig, nil +} + +func (cfg *Config) EndpointUrl() (string, error) { + // Use overridden URL if provided. + if cfg.EndpointURLOverride != "" { + return cfg.EndpointURLOverride, nil + } + return lookupDataCenterURL(cfg.DataCenter) +} + +// dataCenterToURLMapping maps a data center ID to +// to its corresponding OTLP endpoint URL. +var dataCenterToURLMapping = map[string]string{ + "na-01": "otel.collector.na-01.cloud.solarwinds.com:4317", + "na-02": "otel.collector.na-02.cloud.solarwinds.com:4317", + "eu-01": "otel.collector.eu-01.cloud.solarwinds.com:4317", +} + +// lookupDataCenterURL returns the OTLP endpoint URL +// for a `dc` data center ID. Matching is case-insensitive. +// It fails with an error if `dc` doesn't identify a data center. +func lookupDataCenterURL(dc string) (string, error) { + dcLowercase := strings.ToLower(dc) + + url, ok := dataCenterToURLMapping[dcLowercase] + if !ok { + return "", fmt.Errorf("unknown data center ID: %s, valid IDs: %s", dc, slices.Collect(maps.Keys(dataCenterToURLMapping))) + } + + return url, nil +} diff --git a/extension/solarwindsextension/internal/exporter.go b/extension/solarwindsextension/internal/exporter.go new file mode 100644 index 0000000..a131650 --- /dev/null +++ b/extension/solarwindsextension/internal/exporter.go @@ -0,0 +1,75 @@ +package internal + +import ( + "context" + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/exporter" + "go.opentelemetry.io/collector/exporter/otlpexporter" + "go.opentelemetry.io/collector/extension" + "go.opentelemetry.io/collector/pdata/pcommon" + "go.opentelemetry.io/collector/pdata/pmetric" + "go.uber.org/zap" +) + +type Exporter struct { + logger *zap.Logger + exporter exporter.Metrics + modifyResource resourceModifier +} + +type resourceModifier func(resource pcommon.Resource) error + +func NewExporter(ctx context.Context, set extension.Settings, cfg *Config, modifyResource resourceModifier) (*Exporter, error) { + set.Logger.Debug("Creating Exporter") + oCfg, err := cfg.OTLPConfig() + if err != nil { + return nil, err + } + expSet := toExporterSettings(set) + + exp := &Exporter{ + logger: set.Logger, + modifyResource: modifyResource, + } + exp.exporter, err = otlpexporter.NewFactory().CreateMetrics(ctx, expSet, oCfg) + if err != nil { + return nil, err + } + return exp, nil +} + +func (e *Exporter) Start(ctx context.Context, host component.Host) error { + e.logger.Debug("Starting exporter") + return e.exporter.Start(ctx, host) +} + +func (e *Exporter) Shutdown(ctx context.Context) error { + e.logger.Debug("Shutting down exporter") + return e.exporter.Shutdown(ctx) +} + +func toExporterSettings(set extension.Settings) exporter.Settings { + return exporter.Settings{ + ID: set.ID, + TelemetrySettings: set.TelemetrySettings, + BuildInfo: set.BuildInfo, + } +} + +func (e *Exporter) PushMetrics(ctx context.Context, md pmetric.Metrics) error { + if md.MetricCount() == 0 { + // For receivers with no direct output, but scrape pipeline (ie. telegraf) + return nil + } + + rms := md.ResourceMetrics() + + for i := 0; i < rms.Len(); i++ { + rm := rms.At(i) + if err := e.modifyResource(rm.Resource()); err != nil { + return err + } + } + + return e.exporter.ConsumeMetrics(ctx, md) +} diff --git a/extension/solarwindsextension/internal/heartbeat.go b/extension/solarwindsextension/internal/heartbeat.go new file mode 100644 index 0000000..716370c --- /dev/null +++ b/extension/solarwindsextension/internal/heartbeat.go @@ -0,0 +1,91 @@ +package internal + +import ( + "context" + "errors" + "go.opentelemetry.io/collector/pdata/pmetric" + "go.uber.org/zap" + "sync" + "time" +) + +type metricsPusher func(ctx context.Context, md pmetric.Metrics) error +type metricsAdder func(ctx context.Context, md pmetric.Metrics) error + +type Heartbeat struct { + logger *zap.Logger + + cancel context.CancelFunc + startShutdownMtx sync.Mutex + + pushMetrics metricsPusher + addMetrics metricsAdder +} + +var alreadyRunningError = errors.New("heartbeat already running") +var notRunningError = errors.New("heartbeat not started") + +func NewHeartbeat(logger *zap.Logger, pushMetrics metricsPusher, addMetrics metricsAdder) *Heartbeat { + logger.Debug("Creating Heartbeat") + return &Heartbeat{logger: logger, pushMetrics: pushMetrics, addMetrics: addMetrics} +} + +func (h *Heartbeat) Start() error { + h.startShutdownMtx.Lock() + defer h.startShutdownMtx.Unlock() + + h.logger.Debug("Starting Heartbeat routine") + if h.cancel != nil { + return alreadyRunningError + } + var ctx context.Context + ctx, h.cancel = context.WithCancel(context.Background()) + go h.loop(ctx) + return nil +} + +func (h *Heartbeat) Shutdown() error { + h.startShutdownMtx.Lock() + defer h.startShutdownMtx.Unlock() + + h.logger.Debug("Stopping Heartbeat routine") + if h.cancel == nil { + return notRunningError + } + h.cancel() + h.cancel = nil + return nil +} + +func (h *Heartbeat) loop(ctx context.Context) { + tick := time.NewTicker(30 * time.Second) + defer tick.Stop() + + // Start beat + if err := h.generateHeartbeat(ctx); err != nil { + h.logger.Error("Generating heartbeat failed", zap.Error(err)) + } + + for { + select { + case <-tick.C: + if err := h.generateHeartbeat(ctx); err != nil { + h.logger.Error("Generating heartbeat failed", zap.Error(err)) + } + case <-ctx.Done(): + return + } + } + +} + +func (h *Heartbeat) generateHeartbeat(ctx context.Context) error { + h.logger.Debug("Generating heartbeat") + md := pmetric.NewMetrics() + + if err := h.addMetrics(ctx, md); err != nil { + return err + } + + return h.pushMetrics(ctx, md) +} diff --git a/extension/solarwindsextension/internal/metadata/generated_status.go b/extension/solarwindsextension/internal/metadata/generated_status.go new file mode 100644 index 0000000..d55c1a9 --- /dev/null +++ b/extension/solarwindsextension/internal/metadata/generated_status.go @@ -0,0 +1,16 @@ +// Code generated by mdatagen. DO NOT EDIT. + +package metadata + +import ( + "go.opentelemetry.io/collector/component" +) + +var ( + Type = component.MustNewType("solarwinds") + ScopeName = "github.com/solarwinds/solarwinds-otel-collector/extension/solarwindsextension" +) + +const ( + ExtensionStability = component.StabilityLevelDevelopment +) diff --git a/extension/solarwindsextension/internal/uptime_counter.go b/extension/solarwindsextension/internal/uptime_counter.go new file mode 100644 index 0000000..e6c0c07 --- /dev/null +++ b/extension/solarwindsextension/internal/uptime_counter.go @@ -0,0 +1,16 @@ +package internal + +import "time" + +type uptimeCounter struct { + startTimeUnixNano int64 +} + +func newUptimeCounter() *uptimeCounter { + return &uptimeCounter{startTimeUnixNano: time.Now().UnixNano()} +} + +func (u *uptimeCounter) Get() float64 { + // Borrowed from Collector's processor uptime metric + return float64(time.Now().UnixNano()-u.startTimeUnixNano) / 1e9 +} diff --git a/extension/solarwindsextension/internal/uptime_metric.go b/extension/solarwindsextension/internal/uptime_metric.go new file mode 100644 index 0000000..cdaf829 --- /dev/null +++ b/extension/solarwindsextension/internal/uptime_metric.go @@ -0,0 +1,34 @@ +package internal + +import ( + "context" + "github.com/solarwinds/solarwinds-otel-collector/extension/solarwindsextension/internal/metadata" + "go.opentelemetry.io/collector/pdata/pcommon" + "go.opentelemetry.io/collector/pdata/pmetric" + "go.uber.org/zap" + "time" +) + +func NewUptimeMetric(logger *zap.Logger) *UptimeMetric { + logger.Debug("Creating UptimeMetric") + return &UptimeMetric{logger: logger, uptime: newUptimeCounter()} +} + +type UptimeMetric struct { + logger *zap.Logger + uptime *uptimeCounter +} + +func (um *UptimeMetric) AddUptimeMetric(_ context.Context, md pmetric.Metrics) error { + um.logger.Debug("Adding uptime metric") + res := md.ResourceMetrics().AppendEmpty() + scopeMetrics := res.ScopeMetrics().AppendEmpty() + scopeMetrics.Scope().SetName(metadata.ScopeName) + scopeMetrics.Scope().SetVersion("0.0.1") + m := scopeMetrics.Metrics().AppendEmpty() + m.SetName("otelcol.uptime") + dataPoint := m.SetEmptyGauge().DataPoints().AppendEmpty() + dataPoint.SetTimestamp(pcommon.NewTimestampFromTime(time.Now())) + dataPoint.SetDoubleValue(um.uptime.Get()) + return nil +} diff --git a/extension/solarwindsextension/metadata.yaml b/extension/solarwindsextension/metadata.yaml new file mode 100644 index 0000000..37e5a06 --- /dev/null +++ b/extension/solarwindsextension/metadata.yaml @@ -0,0 +1,12 @@ +type: solarwinds +github_project: solarwinds/solarwinds-otel-collector + +status: + class: extension + stability: + development: [ extension ] + +tests: + config: + data_center: "na-01" + token: "THIS-IS-A-TEST" From 07837188bb1e58eaf5736c1acbc71be059dfcf7c Mon Sep 17 00:00:00 2001 From: David Sevcik Date: Tue, 10 Dec 2024 12:49:00 +0100 Subject: [PATCH 02/41] Create extension_test.go --- extension/solarwindsextension/extension_test.go | 1 + 1 file changed, 1 insertion(+) create mode 100644 extension/solarwindsextension/extension_test.go diff --git a/extension/solarwindsextension/extension_test.go b/extension/solarwindsextension/extension_test.go new file mode 100644 index 0000000..5bcde33 --- /dev/null +++ b/extension/solarwindsextension/extension_test.go @@ -0,0 +1 @@ +package solarwindsextension From 8c92f1e62496a54e60723e97976a4323eee80d19 Mon Sep 17 00:00:00 2001 From: David Sevcik Date: Tue, 10 Dec 2024 14:02:53 +0100 Subject: [PATCH 03/41] Update metadata.yaml --- extension/solarwindsextension/metadata.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/extension/solarwindsextension/metadata.yaml b/extension/solarwindsextension/metadata.yaml index 37e5a06..1d55116 100644 --- a/extension/solarwindsextension/metadata.yaml +++ b/extension/solarwindsextension/metadata.yaml @@ -10,3 +10,4 @@ tests: config: data_center: "na-01" token: "THIS-IS-A-TEST" + collector_name: "test-name" From 861044e9441950f51e19a7404dd73f6111482243 Mon Sep 17 00:00:00 2001 From: Marek Simek Date: Tue, 10 Dec 2024 14:16:21 +0100 Subject: [PATCH 04/41] Fix Tests --- exporter/solarwindsexporter/README.md | 2 - .../generated_component_test.go | 40 ------------------- exporter/solarwindsexporter/metadata.yaml | 3 ++ .../solarwindsexporter/solarwinds_exporter.go | 11 ++++- 4 files changed, 13 insertions(+), 43 deletions(-) diff --git a/exporter/solarwindsexporter/README.md b/exporter/solarwindsexporter/README.md index b8886ef..0697f8e 100644 --- a/exporter/solarwindsexporter/README.md +++ b/exporter/solarwindsexporter/README.md @@ -26,8 +26,6 @@ exporters: ```yaml exporters: solarwinds: - token: "YOUR-INGESTION-TOKEN" # No default (mandatory field). - data_center: "na-01" # No default (mandatory field). timeout: "10s" sending_queue: enabled: true diff --git a/exporter/solarwindsexporter/generated_component_test.go b/exporter/solarwindsexporter/generated_component_test.go index 3986db9..70cb777 100644 --- a/exporter/solarwindsexporter/generated_component_test.go +++ b/exporter/solarwindsexporter/generated_component_test.go @@ -71,46 +71,6 @@ func TestComponentLifecycle(t *testing.T) { err = c.Shutdown(context.Background()) require.NoError(t, err) }) - t.Run(tt.name+"-lifecycle", func(t *testing.T) { - c, err := tt.createFn(context.Background(), exportertest.NewNopSettings(), cfg) - require.NoError(t, err) - host := componenttest.NewNopHost() - err = c.Start(context.Background(), host) - require.NoError(t, err) - require.NotPanics(t, func() { - switch tt.name { - case "logs": - e, ok := c.(exporter.Logs) - require.True(t, ok) - logs := generateLifecycleTestLogs() - if !e.Capabilities().MutatesData { - logs.MarkReadOnly() - } - err = e.ConsumeLogs(context.Background(), logs) - case "metrics": - e, ok := c.(exporter.Metrics) - require.True(t, ok) - metrics := generateLifecycleTestMetrics() - if !e.Capabilities().MutatesData { - metrics.MarkReadOnly() - } - err = e.ConsumeMetrics(context.Background(), metrics) - case "traces": - e, ok := c.(exporter.Traces) - require.True(t, ok) - traces := generateLifecycleTestTraces() - if !e.Capabilities().MutatesData { - traces.MarkReadOnly() - } - err = e.ConsumeTraces(context.Background(), traces) - } - }) - - require.NoError(t, err) - - err = c.Shutdown(context.Background()) - require.NoError(t, err) - }) } } diff --git a/exporter/solarwindsexporter/metadata.yaml b/exporter/solarwindsexporter/metadata.yaml index af63ca6..d6602da 100644 --- a/exporter/solarwindsexporter/metadata.yaml +++ b/exporter/solarwindsexporter/metadata.yaml @@ -6,3 +6,6 @@ status: stability: development: [traces, metrics, logs] +tests: + skip_lifecycle: true + diff --git a/exporter/solarwindsexporter/solarwinds_exporter.go b/exporter/solarwindsexporter/solarwinds_exporter.go index 9bee7df..bf62d82 100644 --- a/exporter/solarwindsexporter/solarwinds_exporter.go +++ b/exporter/solarwindsexporter/solarwinds_exporter.go @@ -139,7 +139,7 @@ func findExtension(extensions map[component.ID]component.Component, cfgExtension // If no extension name configured and there is only one // found matching the type, return it. - if len(foundExtensions) == 1 && cfgExtensionID != nil { + if len(foundExtensions) == 1 && cfgExtensionID == nil { return foundExtensions[0] } @@ -166,10 +166,19 @@ func (swiExporter *solarwindsExporter) start(ctx context.Context, host component func (swiExporter *solarwindsExporter) shutdown(ctx context.Context) error { switch swiExporter.exporterType { case metricsExporterType: + if swiExporter.metrics == nil { + return nil + } return swiExporter.metrics.Shutdown(ctx) case logsExporterType: + if swiExporter.logs == nil { + return nil + } return swiExporter.logs.Shutdown(ctx) case tracesExporterType: + if swiExporter.traces == nil { + return nil + } return swiExporter.traces.Shutdown(ctx) default: return fmt.Errorf("unknown exporter type: %v", swiExporter.exporterType) From 07fb2af0147dba474cbb9763931f42febede0ffb Mon Sep 17 00:00:00 2001 From: David Sevcik Date: Tue, 10 Dec 2024 14:40:43 +0100 Subject: [PATCH 05/41] Update go version to 1.23.4 --- cmd/solarwinds-otel-collector/go.mod | 4 +--- exporter/solarwindsexporter/go.mod | 2 +- extension/solarwindsextension/go.mod | 2 +- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/cmd/solarwinds-otel-collector/go.mod b/cmd/solarwinds-otel-collector/go.mod index 85ab6e7..f5dcbd4 100644 --- a/cmd/solarwinds-otel-collector/go.mod +++ b/cmd/solarwinds-otel-collector/go.mod @@ -1,8 +1,6 @@ module github.com/solarwinds/solarwinds-otel-collector -go 1.22.7 - -toolchain go1.22.9 +go 1.23.4 require ( github.com/open-telemetry/opentelemetry-collector-contrib/confmap/provider/aesprovider v0.113.0 diff --git a/exporter/solarwindsexporter/go.mod b/exporter/solarwindsexporter/go.mod index cd802b2..1d84dcf 100644 --- a/exporter/solarwindsexporter/go.mod +++ b/exporter/solarwindsexporter/go.mod @@ -1,6 +1,6 @@ module github.com/solarwinds/solarwinds-otel-collector/exporter/solarwindsexporter -go 1.22.7 +go 1.23.4 require ( github.com/stretchr/testify v1.10.0 diff --git a/extension/solarwindsextension/go.mod b/extension/solarwindsextension/go.mod index 26e0371..c15d324 100644 --- a/extension/solarwindsextension/go.mod +++ b/extension/solarwindsextension/go.mod @@ -1,6 +1,6 @@ module github.com/solarwinds/solarwinds-otel-collector/extension/solarwindsextension -go 1.22.7 +go 1.23.4 require ( github.com/stretchr/testify v1.9.0 From 0db8be1c9ef129fb319e7cd67815330a30f72ee4 Mon Sep 17 00:00:00 2001 From: David Sevcik Date: Tue, 10 Dec 2024 14:42:29 +0100 Subject: [PATCH 06/41] Update go docker image to 1.23.4 --- build/docker/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/docker/Dockerfile b/build/docker/Dockerfile index c0f6dbd..2f850ef 100644 --- a/build/docker/Dockerfile +++ b/build/docker/Dockerfile @@ -1,4 +1,4 @@ -FROM docker.io/library/golang:1.22.7-bookworm AS base +FROM docker.io/library/golang:1.23.4-bookworm AS base COPY /LICENSE /LICENSE COPY ./ /src From b23d5c6872f31fcc7cf9462824ae241cd6ee60f5 Mon Sep 17 00:00:00 2001 From: David Sevcik Date: Tue, 10 Dec 2024 14:48:16 +0100 Subject: [PATCH 07/41] Add licences --- extension/solarwindsextension/doc.go | 14 ++++++++++++++ extension/solarwindsextension/endpoint_config.go | 14 ++++++++++++++ extension/solarwindsextension/extension.go | 14 ++++++++++++++ extension/solarwindsextension/extension_test.go | 14 ++++++++++++++ extension/solarwindsextension/factory.go | 14 ++++++++++++++ extension/solarwindsextension/internal/exporter.go | 14 ++++++++++++++ .../solarwindsextension/internal/heartbeat.go | 14 ++++++++++++++ .../solarwindsextension/internal/uptime_counter.go | 14 ++++++++++++++ .../solarwindsextension/internal/uptime_metric.go | 14 ++++++++++++++ 9 files changed, 126 insertions(+) diff --git a/extension/solarwindsextension/doc.go b/extension/solarwindsextension/doc.go index 159dd9a..3bcd9d8 100644 --- a/extension/solarwindsextension/doc.go +++ b/extension/solarwindsextension/doc.go @@ -1,2 +1,16 @@ +// Copyright 2024 SolarWinds Worldwide, LLC. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + //go:generate mdatagen metadata.yaml package solarwindsextension diff --git a/extension/solarwindsextension/endpoint_config.go b/extension/solarwindsextension/endpoint_config.go index 27fba74..b0aa1a5 100644 --- a/extension/solarwindsextension/endpoint_config.go +++ b/extension/solarwindsextension/endpoint_config.go @@ -1,3 +1,17 @@ +// Copyright 2024 SolarWinds Worldwide, LLC. All rights reserved. +// +// 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 solarwindsextension import ( diff --git a/extension/solarwindsextension/extension.go b/extension/solarwindsextension/extension.go index afcf198..de91f21 100644 --- a/extension/solarwindsextension/extension.go +++ b/extension/solarwindsextension/extension.go @@ -1,3 +1,17 @@ +// Copyright 2024 SolarWinds Worldwide, LLC. All rights reserved. +// +// 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 solarwindsextension import ( diff --git a/extension/solarwindsextension/extension_test.go b/extension/solarwindsextension/extension_test.go index 5bcde33..9bf5784 100644 --- a/extension/solarwindsextension/extension_test.go +++ b/extension/solarwindsextension/extension_test.go @@ -1 +1,15 @@ +// Copyright 2024 SolarWinds Worldwide, LLC. All rights reserved. +// +// 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 solarwindsextension diff --git a/extension/solarwindsextension/factory.go b/extension/solarwindsextension/factory.go index 8c0aa49..fdb530a 100644 --- a/extension/solarwindsextension/factory.go +++ b/extension/solarwindsextension/factory.go @@ -1,3 +1,17 @@ +// Copyright 2024 SolarWinds Worldwide, LLC. All rights reserved. +// +// 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 solarwindsextension import ( diff --git a/extension/solarwindsextension/internal/exporter.go b/extension/solarwindsextension/internal/exporter.go index a131650..2d29f9a 100644 --- a/extension/solarwindsextension/internal/exporter.go +++ b/extension/solarwindsextension/internal/exporter.go @@ -1,3 +1,17 @@ +// Copyright 2024 SolarWinds Worldwide, LLC. All rights reserved. +// +// 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 internal import ( diff --git a/extension/solarwindsextension/internal/heartbeat.go b/extension/solarwindsextension/internal/heartbeat.go index 716370c..f6ce11e 100644 --- a/extension/solarwindsextension/internal/heartbeat.go +++ b/extension/solarwindsextension/internal/heartbeat.go @@ -1,3 +1,17 @@ +// Copyright 2024 SolarWinds Worldwide, LLC. All rights reserved. +// +// 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 internal import ( diff --git a/extension/solarwindsextension/internal/uptime_counter.go b/extension/solarwindsextension/internal/uptime_counter.go index e6c0c07..b551a4b 100644 --- a/extension/solarwindsextension/internal/uptime_counter.go +++ b/extension/solarwindsextension/internal/uptime_counter.go @@ -1,3 +1,17 @@ +// Copyright 2024 SolarWinds Worldwide, LLC. All rights reserved. +// +// 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 internal import "time" diff --git a/extension/solarwindsextension/internal/uptime_metric.go b/extension/solarwindsextension/internal/uptime_metric.go index cdaf829..a314982 100644 --- a/extension/solarwindsextension/internal/uptime_metric.go +++ b/extension/solarwindsextension/internal/uptime_metric.go @@ -1,3 +1,17 @@ +// Copyright 2024 SolarWinds Worldwide, LLC. All rights reserved. +// +// 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 internal import ( From d34d89b803cb1934c2bcaa5ed3c1509f06325162 Mon Sep 17 00:00:00 2001 From: David Sevcik Date: Tue, 10 Dec 2024 15:53:40 +0100 Subject: [PATCH 08/41] Avoid checking generated files --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index d9c71d6..42fa8e2 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,7 @@ include Makefile.Common ALL_SRC := $(shell find . \( -name "*.go" -o -name "*.sh" \) \ + -not -path '*generated*' \ -type f | sort) .PHONY: ci-check-licenses From 6dbe76ea43f151188c004009739c8a699e1c4482 Mon Sep 17 00:00:00 2001 From: David Sevcik Date: Tue, 10 Dec 2024 15:53:50 +0100 Subject: [PATCH 09/41] Fix licence --- extension/solarwindsextension/internal/config.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extension/solarwindsextension/internal/config.go b/extension/solarwindsextension/internal/config.go index cd64582..b06283e 100644 --- a/extension/solarwindsextension/internal/config.go +++ b/extension/solarwindsextension/internal/config.go @@ -2,9 +2,9 @@ // // 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: +// You may obtain a copy of the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// 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, From 39510a55870a858d374592f74b45357b0fc6a90f Mon Sep 17 00:00:00 2001 From: Marek Simek Date: Tue, 10 Dec 2024 16:14:30 +0100 Subject: [PATCH 10/41] Use Correct Iface --- exporter/solarwindsexporter/solarwinds_exporter.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/exporter/solarwindsexporter/solarwinds_exporter.go b/exporter/solarwindsexporter/solarwinds_exporter.go index bf62d82..5972881 100644 --- a/exporter/solarwindsexporter/solarwinds_exporter.go +++ b/exporter/solarwindsexporter/solarwinds_exporter.go @@ -84,12 +84,14 @@ func (swiExporter *solarwindsExporter) initExporterType( return errors.New("solarwinds extension not found") } + endpointCfg := swiExtension.GetEndpointConfig() + // Get token from the extensions. - token := swiExtension.Token() + token := endpointCfg.Token() swiExporter.config.ingestionToken = token // Get URl from the extension. - url, err := swiExtension.Url() + url, err := endpointCfg.Url() if err != nil { return fmt.Errorf(": %w", err) } @@ -118,6 +120,10 @@ func (swiExporter *solarwindsExporter) initExporterType( } type EndpointConfigProvider interface { + GetEndpointConfig() EndpointConfig +} + +type EndpointConfig interface { Url() (string, error) Token() configopaque.String } From 56ef9a3b852b80c4d9a2bcb9a2ef322bce00f7cf Mon Sep 17 00:00:00 2001 From: David Sevcik Date: Wed, 11 Dec 2024 15:27:15 +0100 Subject: [PATCH 11/41] Fix found extension --- exporter/solarwindsexporter/go.mod | 3 +++ .../solarwindsexporter/solarwinds_exporter.go | 21 +++++-------------- 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/exporter/solarwindsexporter/go.mod b/exporter/solarwindsexporter/go.mod index 1d84dcf..c93556f 100644 --- a/exporter/solarwindsexporter/go.mod +++ b/exporter/solarwindsexporter/go.mod @@ -13,6 +13,7 @@ require ( go.opentelemetry.io/collector/exporter v0.113.0 go.opentelemetry.io/collector/exporter/exportertest v0.113.0 go.opentelemetry.io/collector/exporter/otlpexporter v0.113.0 + github.com/solarwinds/solarwinds-otel-collector/extension/solarwindsextension v0.113.0 go.opentelemetry.io/collector/pdata v1.19.0 go.uber.org/goleak v1.3.0 ) @@ -76,3 +77,5 @@ require ( google.golang.org/protobuf v1.35.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) + +replace github.com/solarwinds/solarwinds-otel-collector/extension/solarwindsextension => ../../extension/solarwindsextension diff --git a/exporter/solarwindsexporter/solarwinds_exporter.go b/exporter/solarwindsexporter/solarwinds_exporter.go index 5972881..4e8c875 100644 --- a/exporter/solarwindsexporter/solarwinds_exporter.go +++ b/exporter/solarwindsexporter/solarwinds_exporter.go @@ -18,9 +18,9 @@ import ( "context" "errors" "fmt" + "github.com/solarwinds/solarwinds-otel-collector/extension/solarwindsextension" "go.opentelemetry.io/collector/component" - "go.opentelemetry.io/collector/config/configopaque" "go.opentelemetry.io/collector/exporter" "go.opentelemetry.io/collector/exporter/otlpexporter" "go.opentelemetry.io/collector/pdata/plog" @@ -36,8 +36,6 @@ const ( tracesExporterType ) -var extensionType = component.MustNewType("solarwinds") - type solarwindsExporter struct { exporterType config *Config @@ -75,7 +73,7 @@ func (swiExporter *solarwindsExporter) initExporterType( swiExporter.exporterType = typ var extensionID *component.ID if swiExporter.config.ExtensionName != "" { - id := component.NewIDWithName(extensionType, swiExporter.config.ExtensionName) + id := component.NewIDWithName(solarwindsextension.NewFactory().Type(), swiExporter.config.ExtensionName) extensionID = &id } @@ -119,20 +117,11 @@ func (swiExporter *solarwindsExporter) initExporterType( } -type EndpointConfigProvider interface { - GetEndpointConfig() EndpointConfig -} - -type EndpointConfig interface { - Url() (string, error) - Token() configopaque.String -} - -func findExtension(extensions map[component.ID]component.Component, cfgExtensionID *component.ID) EndpointConfigProvider { - foundExtensions := make([]EndpointConfigProvider, 0) +func findExtension(extensions map[component.ID]component.Component, cfgExtensionID *component.ID) *solarwindsextension.SolarwindsExtension { + foundExtensions := make([]*solarwindsextension.SolarwindsExtension, 0) for foundExtensionID, ext := range extensions { - if swiExtension, ok := ext.(EndpointConfigProvider); ok { + if swiExtension, ok := ext.(*solarwindsextension.SolarwindsExtension); ok { // If configured extension ID is found, return it. if cfgExtensionID != nil && *cfgExtensionID == foundExtensionID { return swiExtension From 66da104c5351cbe30f4985e675b7527a23e2a9b5 Mon Sep 17 00:00:00 2001 From: Marek Simek Date: Wed, 11 Dec 2024 17:13:12 +0100 Subject: [PATCH 12/41] Follow Collector Cfg Format for Extension --- cmd/solarwinds-otel-collector/main.go | 3 +- exporter/solarwindsexporter/README.md | 4 +- exporter/solarwindsexporter/config.go | 38 ++++++++++++- exporter/solarwindsexporter/config_test.go | 54 ++++++++++++++++++- .../solarwindsexporter/solarwinds_exporter.go | 16 ++++-- .../solarwindsexporter/testdata/full.yaml | 2 +- 6 files changed, 108 insertions(+), 9 deletions(-) diff --git a/cmd/solarwinds-otel-collector/main.go b/cmd/solarwinds-otel-collector/main.go index 2dc4d1c..3919c58 100644 --- a/cmd/solarwinds-otel-collector/main.go +++ b/cmd/solarwinds-otel-collector/main.go @@ -16,6 +16,8 @@ package main import ( + "log" + "github.com/open-telemetry/opentelemetry-collector-contrib/confmap/provider/aesprovider" "github.com/open-telemetry/opentelemetry-collector-contrib/confmap/provider/s3provider" "github.com/open-telemetry/opentelemetry-collector-contrib/confmap/provider/secretsmanagerprovider" @@ -27,7 +29,6 @@ import ( "go.opentelemetry.io/collector/confmap/provider/httpsprovider" "go.opentelemetry.io/collector/confmap/provider/yamlprovider" "go.opentelemetry.io/collector/otelcol" - "log" ) func main() { diff --git a/exporter/solarwindsexporter/README.md b/exporter/solarwindsexporter/README.md index 0697f8e..0c53880 100644 --- a/exporter/solarwindsexporter/README.md +++ b/exporter/solarwindsexporter/README.md @@ -13,7 +13,7 @@ SolarWinds Exporter is a convenience wrapper around [OTLP gRPC Exporter](https:/ ## Getting Started -You just need to include the SolarWinds Exporter in your exporter definitions and no additional configuration is needed. It needs to be used together with the Solarwinds Extension. +You just need to include the SolarWinds Exporter in your exporter definitions and no additional configuration is needed. It needs to be used together with the [Solarwinds Extension](../../extension/solarwindsextension). ```yaml exporters: @@ -26,6 +26,7 @@ exporters: ```yaml exporters: solarwinds: + extension: "solarwinds/1" timeout: "10s" sending_queue: enabled: true @@ -39,6 +40,7 @@ exporters: max_interval: "30s" max_elapsed_time: "300s" ``` +- `extension_name` (optional) - A name of an instance of the [Solarwinds Extension](../../extension/solarwindsextension) that this exporter uses to get its configuration from. If there is only a single instance of the extension, it is optional. The format follows the one in the collector configuration. - `timeout` (optional) - Timeout for each attempt to send data to the SaaS service. A timeout of zero disables the timeout. The **default** is `5s`. - `retry_on_failure` (optional) - These options configure the retry behavior. Please refer to the [Exporter Helper documentation](https://github.com/open-telemetry/opentelemetry-collector/blob/main/exporter/exporterhelper/README.md). - `sending_queue` (optional) - These are the options to set queuing in the exporter. A full descriptions can be similarly found in [Exporter Helper documentation](https://github.com/open-telemetry/opentelemetry-collector/blob/main/exporter/exporterhelper/README.md). diff --git a/exporter/solarwindsexporter/config.go b/exporter/solarwindsexporter/config.go index 6ec64bd..f941adf 100644 --- a/exporter/solarwindsexporter/config.go +++ b/exporter/solarwindsexporter/config.go @@ -15,7 +15,9 @@ package solarwindsexporter import ( + "errors" "fmt" + "strings" "time" "go.opentelemetry.io/collector/component" @@ -29,9 +31,9 @@ import ( // Config represents a Solarwinds Exporter configuration. type Config struct { - // ExtensionName identifies a Solarwinds Extension to + // Extension identifies a Solarwinds Extension to // use for obtaining connection credentials in this exporter. - ExtensionName string `mapstructure:"extension_name"` + Extension string `mapstructure:"extension"` // BackoffSettings configures retry behavior of the exporter. // See [configretry.BackOffConfig] documentation. BackoffSettings configretry.BackOffConfig `mapstructure:"retry_on_failure"` @@ -46,6 +48,38 @@ type Config struct { endpointURL string `mapstructure:"-"` } +// ExtensionAsComponent tries to parse `extension` value of the form 'type/name' +// or 'type' from the configuration to [component.ID]. +// It fails with an error if it doesn't follow this form or the 'type' part +// is not a valid [component.Type]. +// +// Safety: it PANICS if `extension` is empty. +func (cfg *Config) ExtensionAsComponent() (component.ID, error) { + parts := strings.Split(cfg.Extension, "/") + + switch len(parts) { + case 1: + extensionType, err := component.NewType(parts[0]) + if err != nil { + return component.ID{}, fmt.Errorf("invalid extension type: %q", parts[0]) + } + return component.NewID(extensionType), nil + case 2: + // Make sure bare '/' fails. + if len(parts[0]) == 0 && len(parts[1]) == 0 { + return component.ID{}, fmt.Errorf("invalid extension format: %q", cfg.Extension) + } + + extensionType, err := component.NewType(parts[0]) + if err != nil { + return component.ID{}, fmt.Errorf("invalid extension type: %q", parts[0]) + } + return component.NewIDWithName(extensionType, parts[1]), nil + default: + return component.ID{}, errors.New("incorrect 'extension' configuration value") + } +} + // NewDefaultConfig creates a new default configuration. func NewDefaultConfig() component.Config { // Using a higher default than OTLP Exporter does (5s) diff --git a/exporter/solarwindsexporter/config_test.go b/exporter/solarwindsexporter/config_test.go index d9e072f..a0ab96c 100644 --- a/exporter/solarwindsexporter/config_test.go +++ b/exporter/solarwindsexporter/config_test.go @@ -21,6 +21,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/config/configretry" "go.opentelemetry.io/collector/confmap" "go.opentelemetry.io/collector/confmap/confmaptest" @@ -51,7 +52,7 @@ func TestConfigUnmarshalFull(t *testing.T) { // Verify the values. assert.Equal(t, &Config{ - ExtensionName: "swo", + Extension: "solarwinds/1", BackoffSettings: configretry.BackOffConfig{ Enabled: false, InitialInterval: 15000000000, @@ -98,3 +99,54 @@ func TestConfigTokenRedacted(t *testing.T) { // It is redacted when printed. assert.Equal(t, "[REDACTED]", cfg.ingestionToken.String()) } + +// TestConfigExtensionAsComponent test basic scenarios arising from +// valid and invalid combinations of extension "type/name" when +// parsed by `Configuration.ExtensionAsComponent()`. +func TestConfigExtensionAsComponent(t *testing.T) { + type test struct { + extension string + component component.ID + ok bool + } + + tests := []test{ + { + extension: "solarwinds", + component: component.MustNewID("solarwinds"), + ok: true, + }, + { + extension: "solarwinds/1", + component: component.MustNewIDWithName("solarwinds", "1"), + ok: true, + }, + { + extension: "solarwinds/", + component: component.MustNewID("solarwinds"), + ok: true, + }, + { + extension: "/", + component: component.ID{}, + ok: false, + }, + { + extension: "/1", + component: component.ID{}, + ok: false, + }, + } + + for _, tc := range tests { + config := &Config{Extension: tc.extension} + id, err := config.ExtensionAsComponent() + + if tc.ok { // A URL should be returned. + require.NoError(t, err) + assert.Equal(t, tc.component, id) + } else { // It should fail. + assert.ErrorContains(t, err, "invalid extension") + } + } +} diff --git a/exporter/solarwindsexporter/solarwinds_exporter.go b/exporter/solarwindsexporter/solarwinds_exporter.go index 4e8c875..44f6f89 100644 --- a/exporter/solarwindsexporter/solarwinds_exporter.go +++ b/exporter/solarwindsexporter/solarwinds_exporter.go @@ -18,6 +18,7 @@ import ( "context" "errors" "fmt" + "github.com/solarwinds/solarwinds-otel-collector/extension/solarwindsextension" "go.opentelemetry.io/collector/component" @@ -72,9 +73,18 @@ func (swiExporter *solarwindsExporter) initExporterType( ) error { swiExporter.exporterType = typ var extensionID *component.ID - if swiExporter.config.ExtensionName != "" { - id := component.NewIDWithName(solarwindsextension.NewFactory().Type(), swiExporter.config.ExtensionName) - extensionID = &id + if swiExporter.config.Extension != "" { + parsedID, err := swiExporter.config.ExtensionAsComponent() + if err != nil { + return fmt.Errorf("failed parsing extension id: %w", err) + } + extensionID = &parsedID + } + + // Only allow the type of the [solarwindsextension]. + if extensionID != nil && + extensionID.Type() != solarwindsextension.NewFactory().Type() { + return fmt.Errorf("unexpected extension type: %s", extensionID.Type()) } swiExtension := findExtension(host.GetExtensions(), extensionID) diff --git a/exporter/solarwindsexporter/testdata/full.yaml b/exporter/solarwindsexporter/testdata/full.yaml index e0b7018..103581b 100644 --- a/exporter/solarwindsexporter/testdata/full.yaml +++ b/exporter/solarwindsexporter/testdata/full.yaml @@ -1,4 +1,4 @@ -extension_name: "swo" +extension: "solarwinds/1" timeout: "20s" sending_queue: enabled: true From f211fbfa9569366ebd8536b7bbc4a833f6bd303a Mon Sep 17 00:00:00 2001 From: Marek Simek Date: Thu, 12 Dec 2024 11:22:09 +0100 Subject: [PATCH 13/41] Update Exporter README --- exporter/solarwindsexporter/README.md | 36 ++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/exporter/solarwindsexporter/README.md b/exporter/solarwindsexporter/README.md index 0c53880..b90c496 100644 --- a/exporter/solarwindsexporter/README.md +++ b/exporter/solarwindsexporter/README.md @@ -13,7 +13,7 @@ SolarWinds Exporter is a convenience wrapper around [OTLP gRPC Exporter](https:/ ## Getting Started -You just need to include the SolarWinds Exporter in your exporter definitions and no additional configuration is needed. It needs to be used together with the [Solarwinds Extension](../../extension/solarwindsextension). +You just need to include the SolarWinds Exporter in your exporter definitions and no additional configuration is needed. It always needs to be used together with the [Solarwinds Extension](../../extension/solarwindsextension). ```yaml exporters: @@ -26,7 +26,7 @@ exporters: ```yaml exporters: solarwinds: - extension: "solarwinds/1" + extension: "solarwinds" timeout: "10s" sending_queue: enabled: true @@ -39,15 +39,43 @@ exporters: multiplier: 1.5 max_interval: "30s" max_elapsed_time: "300s" +extensions: + solarwinds: + token: "TOKEN" + data_center: "na-01" ``` -- `extension_name` (optional) - A name of an instance of the [Solarwinds Extension](../../extension/solarwindsextension) that this exporter uses to get its configuration from. If there is only a single instance of the extension, it is optional. The format follows the one in the collector configuration. -- `timeout` (optional) - Timeout for each attempt to send data to the SaaS service. A timeout of zero disables the timeout. The **default** is `5s`. +> [!TIP] +> You can omit `extension` from the Solarwinds Exporter configuration above as there is only a single instance of the Solarwinds Extension. + +- `extension` (optional) - This name identifies an instance of the [Solarwinds Extension](../../extension/solarwindsextension) to be used by this exporter to obtain its configuration. + If there is only a single instance of the extension, the configuration value is optional. The format mimics the identifier as it occurs in the collector configuration - + `type/name`, e.g `solarwinds` or `solarwinds/1` for multiple instances of the extension. You would use multiple instances for publishing your telemetry to + multiple **SolarWinds Observability SaaS** organizations. +- `timeout` (optional) - Timeout for each attempt to send data to the SaaS service. A timeout of zero disables the timeout. The **default** is `10s`. - `retry_on_failure` (optional) - These options configure the retry behavior. Please refer to the [Exporter Helper documentation](https://github.com/open-telemetry/opentelemetry-collector/blob/main/exporter/exporterhelper/README.md). - `sending_queue` (optional) - These are the options to set queuing in the exporter. A full descriptions can be similarly found in [Exporter Helper documentation](https://github.com/open-telemetry/opentelemetry-collector/blob/main/exporter/exporterhelper/README.md). > [!NOTE] > The format of all durations above follow the [time.ParseDuration](https://pkg.go.dev/time#ParseDuration) format of "Duration" strings. +### Example with Multiple Solarwinds Extensions +```yaml +exporters: + solarwinds: + extension: "solarwinds/1" +extensions: + solarwinds/1: + token: YOUR-INGESTION-TOKEN1" + data_center: "na-01" + solarwinds/2: + token: YOUR-INGESTION-TOKEN2" + data_center: "na-02" +``` +> [!WARNING] +> The `extension` configuration value cannot be omitted in the example above. +> There are multiple instances of the Solarwinds Extension and you need to +> configure which instance to use to obtain configuration for the exporter. + ## Development - **Tests** can be executed with `make test`. - After changes to `metadata.yaml` generated files need to be re-generated with `make generate`. The [mdatagen](http://go.opentelemetry.io/collector/cmd/mdatagen) tool has to be in the `PATH`. From 4b11ae00436867cf45c6ca5f9bd257fb89fdbb15 Mon Sep 17 00:00:00 2001 From: Marek Simek Date: Thu, 12 Dec 2024 11:57:47 +0100 Subject: [PATCH 14/41] Use component.ID.UnmarshallText Directly --- exporter/solarwindsexporter/config.go | 42 +++++---------- exporter/solarwindsexporter/config_test.go | 52 ------------------- .../solarwindsexporter/solarwinds_exporter.go | 10 ++-- 3 files changed, 15 insertions(+), 89 deletions(-) diff --git a/exporter/solarwindsexporter/config.go b/exporter/solarwindsexporter/config.go index f941adf..b4a3919 100644 --- a/exporter/solarwindsexporter/config.go +++ b/exporter/solarwindsexporter/config.go @@ -15,9 +15,7 @@ package solarwindsexporter import ( - "errors" "fmt" - "strings" "time" "go.opentelemetry.io/collector/component" @@ -48,36 +46,20 @@ type Config struct { endpointURL string `mapstructure:"-"` } -// ExtensionAsComponent tries to parse `extension` value of the form 'type/name' -// or 'type' from the configuration to [component.ID]. -// It fails with an error if it doesn't follow this form or the 'type' part -// is not a valid [component.Type]. +// extensionAsComponent tries to parse `extension` value of the form 'type/name' +// or 'type' from the configuration to [component.ID]. If the `extension value is empty, +// it returns `nil` with a `nil` error. // -// Safety: it PANICS if `extension` is empty. -func (cfg *Config) ExtensionAsComponent() (component.ID, error) { - parts := strings.Split(cfg.Extension, "/") - - switch len(parts) { - case 1: - extensionType, err := component.NewType(parts[0]) - if err != nil { - return component.ID{}, fmt.Errorf("invalid extension type: %q", parts[0]) - } - return component.NewID(extensionType), nil - case 2: - // Make sure bare '/' fails. - if len(parts[0]) == 0 && len(parts[1]) == 0 { - return component.ID{}, fmt.Errorf("invalid extension format: %q", cfg.Extension) - } - - extensionType, err := component.NewType(parts[0]) - if err != nil { - return component.ID{}, fmt.Errorf("invalid extension type: %q", parts[0]) - } - return component.NewIDWithName(extensionType, parts[1]), nil - default: - return component.ID{}, errors.New("incorrect 'extension' configuration value") +// It uses [component.ID.UnmarshalText] and behaves accordingly. +func (cfg *Config) extensionAsComponent() (*component.ID, error) { + if cfg.Extension == "" { + return nil, nil } + + parsedID := &component.ID{} + err := parsedID.UnmarshalText([]byte(cfg.Extension)) + + return parsedID, err } // NewDefaultConfig creates a new default configuration. diff --git a/exporter/solarwindsexporter/config_test.go b/exporter/solarwindsexporter/config_test.go index a0ab96c..4f2978d 100644 --- a/exporter/solarwindsexporter/config_test.go +++ b/exporter/solarwindsexporter/config_test.go @@ -21,7 +21,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/config/configretry" "go.opentelemetry.io/collector/confmap" "go.opentelemetry.io/collector/confmap/confmaptest" @@ -99,54 +98,3 @@ func TestConfigTokenRedacted(t *testing.T) { // It is redacted when printed. assert.Equal(t, "[REDACTED]", cfg.ingestionToken.String()) } - -// TestConfigExtensionAsComponent test basic scenarios arising from -// valid and invalid combinations of extension "type/name" when -// parsed by `Configuration.ExtensionAsComponent()`. -func TestConfigExtensionAsComponent(t *testing.T) { - type test struct { - extension string - component component.ID - ok bool - } - - tests := []test{ - { - extension: "solarwinds", - component: component.MustNewID("solarwinds"), - ok: true, - }, - { - extension: "solarwinds/1", - component: component.MustNewIDWithName("solarwinds", "1"), - ok: true, - }, - { - extension: "solarwinds/", - component: component.MustNewID("solarwinds"), - ok: true, - }, - { - extension: "/", - component: component.ID{}, - ok: false, - }, - { - extension: "/1", - component: component.ID{}, - ok: false, - }, - } - - for _, tc := range tests { - config := &Config{Extension: tc.extension} - id, err := config.ExtensionAsComponent() - - if tc.ok { // A URL should be returned. - require.NoError(t, err) - assert.Equal(t, tc.component, id) - } else { // It should fail. - assert.ErrorContains(t, err, "invalid extension") - } - } -} diff --git a/exporter/solarwindsexporter/solarwinds_exporter.go b/exporter/solarwindsexporter/solarwinds_exporter.go index 44f6f89..6f07172 100644 --- a/exporter/solarwindsexporter/solarwinds_exporter.go +++ b/exporter/solarwindsexporter/solarwinds_exporter.go @@ -72,13 +72,9 @@ func (swiExporter *solarwindsExporter) initExporterType( typ exporterType, ) error { swiExporter.exporterType = typ - var extensionID *component.ID - if swiExporter.config.Extension != "" { - parsedID, err := swiExporter.config.ExtensionAsComponent() - if err != nil { - return fmt.Errorf("failed parsing extension id: %w", err) - } - extensionID = &parsedID + extensionID, err := swiExporter.config.extensionAsComponent() + if err != nil { + return fmt.Errorf("failed parsing extension id: %w", err) } // Only allow the type of the [solarwindsextension]. From 2d5574651c725cce86ef2c78d278e619252093ec Mon Sep 17 00:00:00 2001 From: Marek Simek Date: Thu, 12 Dec 2024 13:02:37 +0100 Subject: [PATCH 15/41] Improve Error Handling --- exporter/solarwindsexporter/factory.go | 2 +- exporter/solarwindsexporter/solarwinds_exporter.go | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/exporter/solarwindsexporter/factory.go b/exporter/solarwindsexporter/factory.go index e0dbd35..b2c4c50 100644 --- a/exporter/solarwindsexporter/factory.go +++ b/exporter/solarwindsexporter/factory.go @@ -47,7 +47,7 @@ func createMetricsExporter( metricsExporter, err := newExporter(exporterCfg, settings, metricsExporterType) if err != nil { - return nil, err // TODO: Wrap. + return nil, fmt.Errorf("failed to create exporter: %w", err) } return exporterhelper.NewMetrics( diff --git a/exporter/solarwindsexporter/solarwinds_exporter.go b/exporter/solarwindsexporter/solarwinds_exporter.go index 6f07172..3b48992 100644 --- a/exporter/solarwindsexporter/solarwinds_exporter.go +++ b/exporter/solarwindsexporter/solarwinds_exporter.go @@ -85,6 +85,9 @@ func (swiExporter *solarwindsExporter) initExporterType( swiExtension := findExtension(host.GetExtensions(), extensionID) if swiExtension == nil { + if extensionID != nil { + return fmt.Errorf("solarwinds extension %q not found", extensionID) + } return errors.New("solarwinds extension not found") } @@ -94,10 +97,10 @@ func (swiExporter *solarwindsExporter) initExporterType( token := endpointCfg.Token() swiExporter.config.ingestionToken = token - // Get URl from the extension. + // Get URL from the extension. url, err := endpointCfg.Url() if err != nil { - return fmt.Errorf(": %w", err) + return fmt.Errorf("URL configuration not available: %w", err) } swiExporter.config.endpointURL = url From e2d96df0e2f4b1e407f64414feba550bd496334e Mon Sep 17 00:00:00 2001 From: David Sevcik Date: Thu, 12 Dec 2024 14:14:08 +0100 Subject: [PATCH 16/41] Refactor the heartbeat encapsulation --- extension/solarwindsextension/extension.go | 32 ++------- .../solarwindsextension/internal/exporter.go | 31 ++------ .../solarwindsextension/internal/heartbeat.go | 71 ++++++++++++++----- .../internal/uptime_metric.go | 4 +- 4 files changed, 68 insertions(+), 70 deletions(-) diff --git a/extension/solarwindsextension/extension.go b/extension/solarwindsextension/extension.go index de91f21..b96174e 100644 --- a/extension/solarwindsextension/extension.go +++ b/extension/solarwindsextension/extension.go @@ -16,38 +16,32 @@ package solarwindsextension import ( "context" - "errors" "github.com/solarwinds/solarwinds-otel-collector/extension/solarwindsextension/internal" "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/extension" - "go.opentelemetry.io/collector/pdata/pcommon" "go.uber.org/zap" ) type SolarwindsExtension struct { - logger *zap.Logger - config *internal.Config - uptimeMetric *internal.UptimeMetric - heartbeat *internal.Heartbeat - exporter *internal.Exporter + logger *zap.Logger + config *internal.Config + heartbeat *internal.Heartbeat } func newExtension(ctx context.Context, set extension.Settings, cfg *internal.Config) (*SolarwindsExtension, error) { set.Logger.Info("Creating SolarwindsExtension") set.Logger.Info("Config", zap.Any("config", cfg)) + e := &SolarwindsExtension{ logger: set.Logger, config: cfg, } var err error - e.exporter, err = internal.NewExporter(ctx, set, cfg, e.decorateResourceAttributes) + e.heartbeat, err = internal.NewHeartbeat(ctx, set, cfg) if err != nil { return nil, err } - e.uptimeMetric = internal.NewUptimeMetric(set.Logger) - e.heartbeat = internal.NewHeartbeat(e.logger, e.exporter.PushMetrics, e.uptimeMetric.AddUptimeMetric) - return e, nil } @@ -55,24 +49,12 @@ func (e *SolarwindsExtension) GetEndpointConfig() EndpointConfig { return newEnd func (e *SolarwindsExtension) Start(ctx context.Context, host component.Host) error { e.logger.Info("Starting SolarwindsExtension") - err := e.exporter.Start(ctx, host) - if err != nil { - return err - } - return e.heartbeat.Start() + return e.heartbeat.Start(ctx, host) } func (e *SolarwindsExtension) Shutdown(ctx context.Context) error { e.logger.Info("Shutting down SolarwindsExtension") // Everything must be shut down, regardless of the failure. - return errors.Join( - e.heartbeat.Shutdown(), - e.exporter.Shutdown(ctx)) -} + return e.heartbeat.Shutdown(ctx) -func (e *SolarwindsExtension) decorateResourceAttributes(resource pcommon.Resource) error { - if e.config.CollectorName != "" { - resource.Attributes().PutStr("collector_name", e.config.CollectorName) - } - return nil } diff --git a/extension/solarwindsextension/internal/exporter.go b/extension/solarwindsextension/internal/exporter.go index 2d29f9a..bace073 100644 --- a/extension/solarwindsextension/internal/exporter.go +++ b/extension/solarwindsextension/internal/exporter.go @@ -20,20 +20,16 @@ import ( "go.opentelemetry.io/collector/exporter" "go.opentelemetry.io/collector/exporter/otlpexporter" "go.opentelemetry.io/collector/extension" - "go.opentelemetry.io/collector/pdata/pcommon" "go.opentelemetry.io/collector/pdata/pmetric" "go.uber.org/zap" ) type Exporter struct { - logger *zap.Logger - exporter exporter.Metrics - modifyResource resourceModifier + logger *zap.Logger + exporter exporter.Metrics } -type resourceModifier func(resource pcommon.Resource) error - -func NewExporter(ctx context.Context, set extension.Settings, cfg *Config, modifyResource resourceModifier) (*Exporter, error) { +func newExporter(ctx context.Context, set extension.Settings, cfg *Config) (*Exporter, error) { set.Logger.Debug("Creating Exporter") oCfg, err := cfg.OTLPConfig() if err != nil { @@ -41,10 +37,7 @@ func NewExporter(ctx context.Context, set extension.Settings, cfg *Config, modif } expSet := toExporterSettings(set) - exp := &Exporter{ - logger: set.Logger, - modifyResource: modifyResource, - } + exp := &Exporter{logger: set.Logger} exp.exporter, err = otlpexporter.NewFactory().CreateMetrics(ctx, expSet, oCfg) if err != nil { return nil, err @@ -52,12 +45,12 @@ func NewExporter(ctx context.Context, set extension.Settings, cfg *Config, modif return exp, nil } -func (e *Exporter) Start(ctx context.Context, host component.Host) error { +func (e *Exporter) start(ctx context.Context, host component.Host) error { e.logger.Debug("Starting exporter") return e.exporter.Start(ctx, host) } -func (e *Exporter) Shutdown(ctx context.Context) error { +func (e *Exporter) shutdown(ctx context.Context) error { e.logger.Debug("Shutting down exporter") return e.exporter.Shutdown(ctx) } @@ -70,20 +63,10 @@ func toExporterSettings(set extension.Settings) exporter.Settings { } } -func (e *Exporter) PushMetrics(ctx context.Context, md pmetric.Metrics) error { +func (e *Exporter) push(ctx context.Context, md pmetric.Metrics) error { if md.MetricCount() == 0 { // For receivers with no direct output, but scrape pipeline (ie. telegraf) return nil } - - rms := md.ResourceMetrics() - - for i := 0; i < rms.Len(); i++ { - rm := rms.At(i) - if err := e.modifyResource(rm.Resource()); err != nil { - return err - } - } - return e.exporter.ConsumeMetrics(ctx, md) } diff --git a/extension/solarwindsextension/internal/heartbeat.go b/extension/solarwindsextension/internal/heartbeat.go index f6ce11e..b49c259 100644 --- a/extension/solarwindsextension/internal/heartbeat.go +++ b/extension/solarwindsextension/internal/heartbeat.go @@ -17,34 +17,47 @@ package internal import ( "context" "errors" + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/extension" + "go.opentelemetry.io/collector/pdata/pcommon" "go.opentelemetry.io/collector/pdata/pmetric" "go.uber.org/zap" "sync" "time" ) -type metricsPusher func(ctx context.Context, md pmetric.Metrics) error -type metricsAdder func(ctx context.Context, md pmetric.Metrics) error - type Heartbeat struct { logger *zap.Logger cancel context.CancelFunc startShutdownMtx sync.Mutex - pushMetrics metricsPusher - addMetrics metricsAdder + metric *UptimeMetric + exporter *Exporter + collectorName string } var alreadyRunningError = errors.New("heartbeat already running") var notRunningError = errors.New("heartbeat not started") -func NewHeartbeat(logger *zap.Logger, pushMetrics metricsPusher, addMetrics metricsAdder) *Heartbeat { - logger.Debug("Creating Heartbeat") - return &Heartbeat{logger: logger, pushMetrics: pushMetrics, addMetrics: addMetrics} +func NewHeartbeat(ctx context.Context, set extension.Settings, cfg *Config) (*Heartbeat, error) { + set.Logger.Debug("Creating Heartbeat") + h := &Heartbeat{ + logger: set.Logger, + metric: newUptimeMetric(set.Logger), + collectorName: cfg.CollectorName, + } + + var err error + h.exporter, err = newExporter(ctx, set, cfg) + if err != nil { + return nil, err + } + + return h, nil } -func (h *Heartbeat) Start() error { +func (h *Heartbeat) Start(ctx context.Context, host component.Host) error { h.startShutdownMtx.Lock() defer h.startShutdownMtx.Unlock() @@ -52,13 +65,19 @@ func (h *Heartbeat) Start() error { if h.cancel != nil { return alreadyRunningError } - var ctx context.Context - ctx, h.cancel = context.WithCancel(context.Background()) - go h.loop(ctx) + + err := h.exporter.start(ctx, host) + if err != nil { + return err + } + + var loopCtx context.Context + loopCtx, h.cancel = context.WithCancel(context.Background()) + go h.loop(loopCtx) return nil } -func (h *Heartbeat) Shutdown() error { +func (h *Heartbeat) Shutdown(ctx context.Context) error { h.startShutdownMtx.Lock() defer h.startShutdownMtx.Unlock() @@ -68,7 +87,7 @@ func (h *Heartbeat) Shutdown() error { } h.cancel() h.cancel = nil - return nil + return h.exporter.shutdown(ctx) } func (h *Heartbeat) loop(ctx context.Context) { @@ -76,14 +95,14 @@ func (h *Heartbeat) loop(ctx context.Context) { defer tick.Stop() // Start beat - if err := h.generateHeartbeat(ctx); err != nil { + if err := h.generate(ctx); err != nil { h.logger.Error("Generating heartbeat failed", zap.Error(err)) } for { select { case <-tick.C: - if err := h.generateHeartbeat(ctx); err != nil { + if err := h.generate(ctx); err != nil { h.logger.Error("Generating heartbeat failed", zap.Error(err)) } case <-ctx.Done(): @@ -93,13 +112,27 @@ func (h *Heartbeat) loop(ctx context.Context) { } -func (h *Heartbeat) generateHeartbeat(ctx context.Context) error { +func (h *Heartbeat) generate(ctx context.Context) error { h.logger.Debug("Generating heartbeat") md := pmetric.NewMetrics() - if err := h.addMetrics(ctx, md); err != nil { + if err := h.metric.add(ctx, md); err != nil { return err } - return h.pushMetrics(ctx, md) + for i, rms := 0, md.ResourceMetrics(); i < rms.Len(); i++ { + rm := rms.At(i) + if err := h.decorateResourceAttributes(rm.Resource()); err != nil { + return err + } + } + + return h.exporter.push(ctx, md) +} + +func (h *Heartbeat) decorateResourceAttributes(resource pcommon.Resource) error { + if h.collectorName != "" { + resource.Attributes().PutStr("collector_name", h.collectorName) + } + return nil } diff --git a/extension/solarwindsextension/internal/uptime_metric.go b/extension/solarwindsextension/internal/uptime_metric.go index a314982..07a235c 100644 --- a/extension/solarwindsextension/internal/uptime_metric.go +++ b/extension/solarwindsextension/internal/uptime_metric.go @@ -23,7 +23,7 @@ import ( "time" ) -func NewUptimeMetric(logger *zap.Logger) *UptimeMetric { +func newUptimeMetric(logger *zap.Logger) *UptimeMetric { logger.Debug("Creating UptimeMetric") return &UptimeMetric{logger: logger, uptime: newUptimeCounter()} } @@ -33,7 +33,7 @@ type UptimeMetric struct { uptime *uptimeCounter } -func (um *UptimeMetric) AddUptimeMetric(_ context.Context, md pmetric.Metrics) error { +func (um *UptimeMetric) add(_ context.Context, md pmetric.Metrics) error { um.logger.Debug("Adding uptime metric") res := md.ResourceMetrics().AppendEmpty() scopeMetrics := res.ScopeMetrics().AppendEmpty() From f5e74b13bc37e6cc893720bfb99fa9ac1e0d8727 Mon Sep 17 00:00:00 2001 From: David Sevcik Date: Thu, 12 Dec 2024 14:16:54 +0100 Subject: [PATCH 17/41] Fix shutdown --- extension/solarwindsextension/internal/heartbeat.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extension/solarwindsextension/internal/heartbeat.go b/extension/solarwindsextension/internal/heartbeat.go index b49c259..1d4e204 100644 --- a/extension/solarwindsextension/internal/heartbeat.go +++ b/extension/solarwindsextension/internal/heartbeat.go @@ -38,7 +38,6 @@ type Heartbeat struct { } var alreadyRunningError = errors.New("heartbeat already running") -var notRunningError = errors.New("heartbeat not started") func NewHeartbeat(ctx context.Context, set extension.Settings, cfg *Config) (*Heartbeat, error) { set.Logger.Debug("Creating Heartbeat") @@ -83,7 +82,8 @@ func (h *Heartbeat) Shutdown(ctx context.Context) error { h.logger.Debug("Stopping Heartbeat routine") if h.cancel == nil { - return notRunningError + // already stopped + return nil } h.cancel() h.cancel = nil From 9748518f337e592e59b2bbdfb1fabef3dfa1c2fb Mon Sep 17 00:00:00 2001 From: David Sevcik Date: Thu, 12 Dec 2024 15:57:55 +0100 Subject: [PATCH 18/41] Pipeline break test --- extension/solarwindsextension/internal/heartbeat.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extension/solarwindsextension/internal/heartbeat.go b/extension/solarwindsextension/internal/heartbeat.go index 1d4e204..b13fdbd 100644 --- a/extension/solarwindsextension/internal/heartbeat.go +++ b/extension/solarwindsextension/internal/heartbeat.go @@ -83,7 +83,7 @@ func (h *Heartbeat) Shutdown(ctx context.Context) error { h.logger.Debug("Stopping Heartbeat routine") if h.cancel == nil { // already stopped - return nil + return errors.New("heartbeat already stopped") } h.cancel() h.cancel = nil From dfb5a5d353e00de407b48ddfcf79881130a560fa Mon Sep 17 00:00:00 2001 From: David Sevcik Date: Thu, 12 Dec 2024 16:07:57 +0100 Subject: [PATCH 19/41] Fix not breaking the pipeline when tests fail --- build/docker/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/docker/Dockerfile b/build/docker/Dockerfile index 2f850ef..af89a10 100644 --- a/build/docker/Dockerfile +++ b/build/docker/Dockerfile @@ -8,7 +8,7 @@ FROM base AS builder RUN cd /src/cmd/solarwinds-otel-collector && CGO_ENABLED=0 GOEXPERIMENT=boringcrypto go build -trimpath -o /src/bin/solarwinds-otel-collector "-ldflags=-s -w" FROM builder AS tests -WORKDIR src +WORKDIR /src # run tests for go modules of all maintained components # image build is stopped if test failure is detected RUN find . -name go.mod -not -path "./cmd/solarwinds-otel-collector/*" -execdir go test ./... \; | ( ! grep FAIL ) From cd620deb7e9f1857c0f54240c946bbd91bba7b87 Mon Sep 17 00:00:00 2001 From: David Sevcik Date: Thu, 12 Dec 2024 16:24:53 +0100 Subject: [PATCH 20/41] Fix test --- extension/solarwindsextension/internal/heartbeat.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extension/solarwindsextension/internal/heartbeat.go b/extension/solarwindsextension/internal/heartbeat.go index b13fdbd..1d4e204 100644 --- a/extension/solarwindsextension/internal/heartbeat.go +++ b/extension/solarwindsextension/internal/heartbeat.go @@ -83,7 +83,7 @@ func (h *Heartbeat) Shutdown(ctx context.Context) error { h.logger.Debug("Stopping Heartbeat routine") if h.cancel == nil { // already stopped - return errors.New("heartbeat already stopped") + return nil } h.cancel() h.cancel = nil From 16cb6d4954b80f18886e214af94d2722749107ef Mon Sep 17 00:00:00 2001 From: Marek Simek Date: Thu, 12 Dec 2024 16:41:59 +0100 Subject: [PATCH 21/41] Use Sentinel Error --- exporter/solarwindsexporter/solarwinds_exporter.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/exporter/solarwindsexporter/solarwinds_exporter.go b/exporter/solarwindsexporter/solarwinds_exporter.go index 3b48992..005e272 100644 --- a/exporter/solarwindsexporter/solarwinds_exporter.go +++ b/exporter/solarwindsexporter/solarwinds_exporter.go @@ -37,6 +37,10 @@ const ( tracesExporterType ) +var ( + ErrSwiExtensionNotFound = errors.New("solarwinds extension not found") +) + type solarwindsExporter struct { exporterType config *Config @@ -88,7 +92,7 @@ func (swiExporter *solarwindsExporter) initExporterType( if extensionID != nil { return fmt.Errorf("solarwinds extension %q not found", extensionID) } - return errors.New("solarwinds extension not found") + return ErrSwiExtensionNotFound } endpointCfg := swiExtension.GetEndpointConfig() From 5c987888becb7af855d10c766bd913e6cac1ecd1 Mon Sep 17 00:00:00 2001 From: Marek Simek Date: Fri, 13 Dec 2024 16:24:40 +0100 Subject: [PATCH 22/41] Add Simple Test for Hearthbeat --- .../solarwindsextension/endpoint_config.go | 3 +- extension/solarwindsextension/extension.go | 4 +- extension/solarwindsextension/factory.go | 6 +- .../solarwindsextension/internal/config.go | 7 +- .../solarwindsextension/internal/exporter.go | 5 +- .../solarwindsextension/internal/heartbeat.go | 47 +++++++--- .../internal/hearthbeat_test.go | 92 +++++++++++++++++++ .../internal/uptime_metric.go | 8 +- 8 files changed, 145 insertions(+), 27 deletions(-) create mode 100644 extension/solarwindsextension/internal/hearthbeat_test.go diff --git a/extension/solarwindsextension/endpoint_config.go b/extension/solarwindsextension/endpoint_config.go index b0aa1a5..aca02b3 100644 --- a/extension/solarwindsextension/endpoint_config.go +++ b/extension/solarwindsextension/endpoint_config.go @@ -15,8 +15,9 @@ package solarwindsextension import ( - "github.com/solarwinds/solarwinds-otel-collector/extension/solarwindsextension/internal" "go.opentelemetry.io/collector/config/configopaque" + + "github.com/solarwinds/solarwinds-otel-collector/extension/solarwindsextension/internal" ) type EndpointConfig interface { diff --git a/extension/solarwindsextension/extension.go b/extension/solarwindsextension/extension.go index b96174e..f7a790f 100644 --- a/extension/solarwindsextension/extension.go +++ b/extension/solarwindsextension/extension.go @@ -16,10 +16,12 @@ package solarwindsextension import ( "context" - "github.com/solarwinds/solarwinds-otel-collector/extension/solarwindsextension/internal" + "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/extension" "go.uber.org/zap" + + "github.com/solarwinds/solarwinds-otel-collector/extension/solarwindsextension/internal" ) type SolarwindsExtension struct { diff --git a/extension/solarwindsextension/factory.go b/extension/solarwindsextension/factory.go index fdb530a..c0f40e5 100644 --- a/extension/solarwindsextension/factory.go +++ b/extension/solarwindsextension/factory.go @@ -17,10 +17,12 @@ package solarwindsextension import ( "context" "fmt" - "github.com/solarwinds/solarwinds-otel-collector/extension/solarwindsextension/internal" - "github.com/solarwinds/solarwinds-otel-collector/extension/solarwindsextension/internal/metadata" + "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/extension" + + "github.com/solarwinds/solarwinds-otel-collector/extension/solarwindsextension/internal" + "github.com/solarwinds/solarwinds-otel-collector/extension/solarwindsextension/internal/metadata" ) func NewFactory() extension.Factory { diff --git a/extension/solarwindsextension/internal/config.go b/extension/solarwindsextension/internal/config.go index b06283e..6c05e55 100644 --- a/extension/solarwindsextension/internal/config.go +++ b/extension/solarwindsextension/internal/config.go @@ -17,14 +17,15 @@ package internal import ( "errors" "fmt" + "maps" + "slices" + "strings" + "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/config/configgrpc" "go.opentelemetry.io/collector/config/configopaque" "go.opentelemetry.io/collector/config/configtls" "go.opentelemetry.io/collector/exporter/otlpexporter" - "maps" - "slices" - "strings" ) // Config represents a Solarwinds Exporter configuration. diff --git a/extension/solarwindsextension/internal/exporter.go b/extension/solarwindsextension/internal/exporter.go index bace073..bc97f8b 100644 --- a/extension/solarwindsextension/internal/exporter.go +++ b/extension/solarwindsextension/internal/exporter.go @@ -16,6 +16,7 @@ package internal import ( "context" + "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/exporter" "go.opentelemetry.io/collector/exporter/otlpexporter" @@ -64,9 +65,5 @@ func toExporterSettings(set extension.Settings) exporter.Settings { } func (e *Exporter) push(ctx context.Context, md pmetric.Metrics) error { - if md.MetricCount() == 0 { - // For receivers with no direct output, but scrape pipeline (ie. telegraf) - return nil - } return e.exporter.ConsumeMetrics(ctx, md) } diff --git a/extension/solarwindsextension/internal/heartbeat.go b/extension/solarwindsextension/internal/heartbeat.go index 1d4e204..df295be 100644 --- a/extension/solarwindsextension/internal/heartbeat.go +++ b/extension/solarwindsextension/internal/heartbeat.go @@ -17,15 +17,26 @@ package internal import ( "context" "errors" + "sync" + "time" + "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/extension" "go.opentelemetry.io/collector/pdata/pcommon" "go.opentelemetry.io/collector/pdata/pmetric" "go.uber.org/zap" - "sync" - "time" ) +const ( + defaultHeartbeatInterval = 30 * time.Second +) + +type MetricsExporter interface { + start(context.Context, component.Host) error + shutdown(context.Context) error + push(context.Context, pmetric.Metrics) error +} + type Heartbeat struct { logger *zap.Logger @@ -33,27 +44,37 @@ type Heartbeat struct { startShutdownMtx sync.Mutex metric *UptimeMetric - exporter *Exporter + exporter MetricsExporter collectorName string + + beatInterval time.Duration } var alreadyRunningError = errors.New("heartbeat already running") func NewHeartbeat(ctx context.Context, set extension.Settings, cfg *Config) (*Heartbeat, error) { set.Logger.Debug("Creating Heartbeat") - h := &Heartbeat{ - logger: set.Logger, - metric: newUptimeMetric(set.Logger), - collectorName: cfg.CollectorName, - } - var err error - h.exporter, err = newExporter(ctx, set, cfg) + exp, err := newExporter(ctx, set, cfg) if err != nil { return nil, err } - return h, nil + return newHeartbeatWithExporter(set, cfg, exp), nil +} + +func newHeartbeatWithExporter( + set extension.Settings, + cfg *Config, + exporter MetricsExporter, +) *Heartbeat { + return &Heartbeat{ + logger: set.Logger, + metric: newUptimeMetric(set.Logger), + collectorName: cfg.CollectorName, + exporter: exporter, + beatInterval: defaultHeartbeatInterval, + } } func (h *Heartbeat) Start(ctx context.Context, host component.Host) error { @@ -91,7 +112,7 @@ func (h *Heartbeat) Shutdown(ctx context.Context) error { } func (h *Heartbeat) loop(ctx context.Context) { - tick := time.NewTicker(30 * time.Second) + tick := time.NewTicker(h.beatInterval) defer tick.Stop() // Start beat @@ -132,7 +153,7 @@ func (h *Heartbeat) generate(ctx context.Context) error { func (h *Heartbeat) decorateResourceAttributes(resource pcommon.Resource) error { if h.collectorName != "" { - resource.Attributes().PutStr("collector_name", h.collectorName) + resource.Attributes().PutStr("sw.otelcol.collector.name", h.collectorName) } return nil } diff --git a/extension/solarwindsextension/internal/hearthbeat_test.go b/extension/solarwindsextension/internal/hearthbeat_test.go new file mode 100644 index 0000000..e9abe4f --- /dev/null +++ b/extension/solarwindsextension/internal/hearthbeat_test.go @@ -0,0 +1,92 @@ +// Copyright 2024 SolarWinds Worldwide, LLC. All rights reserved. +// +// 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 internal + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/component/componenttest" + "go.opentelemetry.io/collector/extension/extensiontest" + "go.opentelemetry.io/collector/pdata/pmetric" +) + +type mockExporter struct { + pushed []pmetric.Metrics +} + +func newMockExporter() *mockExporter { + return &mockExporter{ + pushed: []pmetric.Metrics{}, + } +} + +func (m *mockExporter) start(_ context.Context, _ component.Host) error { + return nil +} + +func (m *mockExporter) shutdown(_ context.Context) error { + return nil +} + +func (m *mockExporter) push(_ context.Context, metrics pmetric.Metrics) error { + m.pushed = append(m.pushed, metrics) + + return nil +} + +// TestHeartbeatEmittingMetrics runs the `Hearthbeat` +// in isolation with a mocked exporter and inspects +// emitted metrics. +func TestHeartbeatEmittingMetrics(t *testing.T) { + const testDuration = 1000 * time.Millisecond + const beatInterval = 100 * time.Millisecond + const expectedCount = int(testDuration / beatInterval) + + mockExp := newMockExporter() + hb := newHeartbeatWithExporter( + extensiontest.NewNopSettings(), + &Config{}, + mockExp, + ) + // Adjust the heartbeat interval to shave off some time. + hb.beatInterval = beatInterval + + // Start the heartbeat. + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + err := hb.Start(ctx, componenttest.NewNopHost()) + require.NoError(t, err) + + // Inspect exported metrics in a loop. + assert.Eventuallyf( + t, + func() bool { + return len(mockExp.pushed) == expectedCount + }, + testDuration+50*time.Millisecond, // Allow some leeway. + 10*time.Millisecond, + "expected %d metrics, got %d", + expectedCount, + len(mockExp.pushed), + ) + + err = hb.Shutdown(ctx) + require.NoError(t, err) +} diff --git a/extension/solarwindsextension/internal/uptime_metric.go b/extension/solarwindsextension/internal/uptime_metric.go index 07a235c..3b9cba8 100644 --- a/extension/solarwindsextension/internal/uptime_metric.go +++ b/extension/solarwindsextension/internal/uptime_metric.go @@ -16,11 +16,13 @@ package internal import ( "context" - "github.com/solarwinds/solarwinds-otel-collector/extension/solarwindsextension/internal/metadata" + "time" + "go.opentelemetry.io/collector/pdata/pcommon" "go.opentelemetry.io/collector/pdata/pmetric" "go.uber.org/zap" - "time" + + "github.com/solarwinds/solarwinds-otel-collector/extension/solarwindsextension/internal/metadata" ) func newUptimeMetric(logger *zap.Logger) *UptimeMetric { @@ -40,7 +42,7 @@ func (um *UptimeMetric) add(_ context.Context, md pmetric.Metrics) error { scopeMetrics.Scope().SetName(metadata.ScopeName) scopeMetrics.Scope().SetVersion("0.0.1") m := scopeMetrics.Metrics().AppendEmpty() - m.SetName("otelcol.uptime") + m.SetName("sw.otelcol.uptime") dataPoint := m.SetEmptyGauge().DataPoints().AppendEmpty() dataPoint.SetTimestamp(pcommon.NewTimestampFromTime(time.Now())) dataPoint.SetDoubleValue(um.uptime.Get()) From 535b3a1eca2948f37ec08e3dc9ff5303ead36a90 Mon Sep 17 00:00:00 2001 From: Marek Simek Date: Mon, 16 Dec 2024 13:49:55 +0100 Subject: [PATCH 23/41] Add Config Tests --- exporter/solarwindsexporter/config_test.go | 22 +-- exporter/solarwindsexporter/go.mod | 7 +- exporter/solarwindsexporter/go.sum | 8 +- extension/solarwindsextension/config_test.go | 154 ++++++++++++++++++ extension/solarwindsextension/go.mod | 9 +- extension/solarwindsextension/go.sum | 12 +- .../internal/datacell_test.go | 52 ++++++ .../internal/hearthbeat_test.go | 10 +- .../solarwindsextension/testdata/full.yaml | 4 + .../solarwindsextension/testdata/minimal.yaml | 3 + .../testdata/missing_collector_name.yaml | 2 + .../testdata/missing_dc.yaml | 2 + .../testdata/missing_token.yaml | 2 + .../testdata/url_override.yaml | 4 + pkg/testutil/config.go | 37 +++++ pkg/testutil/go.mod | 21 +++ pkg/testutil/go.sum | 37 +++++ 17 files changed, 349 insertions(+), 37 deletions(-) create mode 100644 extension/solarwindsextension/config_test.go create mode 100644 extension/solarwindsextension/internal/datacell_test.go create mode 100644 extension/solarwindsextension/testdata/full.yaml create mode 100644 extension/solarwindsextension/testdata/minimal.yaml create mode 100644 extension/solarwindsextension/testdata/missing_collector_name.yaml create mode 100644 extension/solarwindsextension/testdata/missing_dc.yaml create mode 100644 extension/solarwindsextension/testdata/missing_token.yaml create mode 100644 extension/solarwindsextension/testdata/url_override.yaml create mode 100644 pkg/testutil/config.go create mode 100644 pkg/testutil/go.mod create mode 100644 pkg/testutil/go.sum diff --git a/exporter/solarwindsexporter/config_test.go b/exporter/solarwindsexporter/config_test.go index 4f2978d..9c48309 100644 --- a/exporter/solarwindsexporter/config_test.go +++ b/exporter/solarwindsexporter/config_test.go @@ -15,34 +15,20 @@ package solarwindsexporter import ( - "fmt" - "path/filepath" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/collector/config/configretry" - "go.opentelemetry.io/collector/confmap" - "go.opentelemetry.io/collector/confmap/confmaptest" "go.opentelemetry.io/collector/exporter/exporterhelper" -) - -// loadConfigTestdata is a helper function to load a testdata -// file by its name. -func loadConfigTestdata(t *testing.T, name string) *confmap.Conf { - t.Helper() - - filename := fmt.Sprintf("%s.yaml", name) - cm, err := confmaptest.LoadConf(filepath.Join("testdata", filename)) - require.NoError(t, err) - return cm -} + "github.com/solarwinds/solarwinds-otel-collector/pkg/testutil" +) // TestConfigUnmarshalFull tries to parse a configuration file // with all values provided and verifies the configuration. func TestConfigUnmarshalFull(t *testing.T) { - cfgFile := loadConfigTestdata(t, "full") + cfgFile := testutil.LoadConfigTestdata(t, "full") // Parse configuration. factory := NewFactory() @@ -75,7 +61,7 @@ func TestConfigUnmarshalFull(t *testing.T) { // file containing only the mandatory values successfully // validates. func TestConfigValidateOK(t *testing.T) { - cfgFile := loadConfigTestdata(t, "minimal") + cfgFile := testutil.LoadConfigTestdata(t, "minimal") // Parse configuration. factory := NewFactory() diff --git a/exporter/solarwindsexporter/go.mod b/exporter/solarwindsexporter/go.mod index c93556f..1c9e4a7 100644 --- a/exporter/solarwindsexporter/go.mod +++ b/exporter/solarwindsexporter/go.mod @@ -3,17 +3,18 @@ module github.com/solarwinds/solarwinds-otel-collector/exporter/solarwindsexport go 1.23.4 require ( + github.com/solarwinds/solarwinds-otel-collector/extension/solarwindsextension v0.113.0 + github.com/solarwinds/solarwinds-otel-collector/pkg/testutil v0.0.0-00010101000000-000000000000 github.com/stretchr/testify v1.10.0 go.opentelemetry.io/collector/component v0.113.0 go.opentelemetry.io/collector/config/configgrpc v0.113.0 go.opentelemetry.io/collector/config/configopaque v1.19.0 go.opentelemetry.io/collector/config/configretry v1.19.0 go.opentelemetry.io/collector/config/configtls v1.19.0 - go.opentelemetry.io/collector/confmap v1.19.0 + go.opentelemetry.io/collector/confmap v1.21.0 go.opentelemetry.io/collector/exporter v0.113.0 go.opentelemetry.io/collector/exporter/exportertest v0.113.0 go.opentelemetry.io/collector/exporter/otlpexporter v0.113.0 - github.com/solarwinds/solarwinds-otel-collector/extension/solarwindsextension v0.113.0 go.opentelemetry.io/collector/pdata v1.19.0 go.uber.org/goleak v1.3.0 ) @@ -79,3 +80,5 @@ require ( ) replace github.com/solarwinds/solarwinds-otel-collector/extension/solarwindsextension => ../../extension/solarwindsextension + +replace github.com/solarwinds/solarwinds-otel-collector/pkg/testutil => ../../pkg/testutil diff --git a/exporter/solarwindsexporter/go.sum b/exporter/solarwindsexporter/go.sum index 5c8bbed..21684c7 100644 --- a/exporter/solarwindsexporter/go.sum +++ b/exporter/solarwindsexporter/go.sum @@ -52,8 +52,8 @@ github.com/mostynb/go-grpc-compression v1.2.3 h1:42/BKWMy0KEJGSdWvzqIyOZ95YcR9mL github.com/mostynb/go-grpc-compression v1.2.3/go.mod h1:AghIxF3P57umzqM9yz795+y1Vjs47Km/Y2FE6ouQ7Lg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= @@ -84,8 +84,8 @@ go.opentelemetry.io/collector/config/configtls v1.19.0 h1:GQ/cF1hgNqHVBq2oSSrOFX go.opentelemetry.io/collector/config/configtls v1.19.0/go.mod h1:1hyqnYB3JqEUlk1ME/s9HYz4oCRcxQCRxsJitFFT/cA= go.opentelemetry.io/collector/config/internal v0.114.0 h1:uWSDWTJb8T6xRjKD9/XmEARakXnxgYVYKUeId78hErc= go.opentelemetry.io/collector/config/internal v0.114.0/go.mod h1:yC7E4h1Uj0SubxcFImh6OvBHFTjMh99+A5PuyIgDWqc= -go.opentelemetry.io/collector/confmap v1.19.0 h1:TQ0lZpAKqgsE0EKk+u4JA+uBbPYeFRmWP3GH43w40CY= -go.opentelemetry.io/collector/confmap v1.19.0/go.mod h1:GgNu1ElPGmLn9govqIfjaopvdspw4PJ9KeDtWC4E2Q4= +go.opentelemetry.io/collector/confmap v1.21.0 h1:1tIcx2/Suwg8VhuPmQw87ba0ludPmumpFCFRZZa6RXA= +go.opentelemetry.io/collector/confmap v1.21.0/go.mod h1:Rrhs+MWoaP6AswZp+ReQ2VO9dfOfcUjdjiSHBsG+nec= go.opentelemetry.io/collector/consumer v0.113.0 h1:KJSiK5vSIY9dgPxwKfQ3gOgKtQsqc+7IB7mGhUAL5c8= go.opentelemetry.io/collector/consumer v0.113.0/go.mod h1:zHMlXYFaJlZoLCBR6UwWoyXZ/adcO1u2ydqUal3VmYU= go.opentelemetry.io/collector/consumer/consumererror v0.113.0 h1:Hd2N7n9RKbnKRaVrdw6fPBoQko5zZIgCxwVxkL6SAIE= diff --git a/extension/solarwindsextension/config_test.go b/extension/solarwindsextension/config_test.go new file mode 100644 index 0000000..391088f --- /dev/null +++ b/extension/solarwindsextension/config_test.go @@ -0,0 +1,154 @@ +// Copyright 2024 SolarWinds Worldwide, LLC. All rights reserved. +// +// 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 solarwindsextension + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/config/configopaque" + + "github.com/solarwinds/solarwinds-otel-collector/pkg/testutil" + + "github.com/solarwinds/solarwinds-otel-collector/extension/solarwindsextension/internal" +) + +// TestConfigUnmarshalFull tries to parse a configuration file +// with all values provided and verifies the configuration. +func TestConfigUnmarshalFull(t *testing.T) { + cfgFile := testutil.LoadConfigTestdata(t, "full") + + // Parse configuration. + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + require.NoError(t, cfgFile.Unmarshal(&cfg)) + + // Verify the values. + assert.Equal(t, &internal.Config{ + DataCenter: "na-01", + EndpointURLOverride: "127.0.0.1:1234", + IngestionToken: "TOKEN", + CollectorName: "best-collector-ever", + }, cfg) +} + +// TestConfigValidateOK verifies that a configuration +// file containing only the mandatory values successfully +// validates. +func TestConfigValidateOK(t *testing.T) { + cfgFile := testutil.LoadConfigTestdata(t, "minimal") + + // Parse configuration. + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + require.NoError(t, cfgFile.Unmarshal(&cfg)) + + // Try to validate it. + assert.NoError(t, cfg.(*internal.Config).Validate()) +} + +// TestConfigValidateMissingToken verifies that +// the validation of a configuration file with +// the token missing fails as expected. +func TestConfigValidateMissingToken(t *testing.T) { + cfgFile := testutil.LoadConfigTestdata(t, "missing_token") + + // Parse configuration. + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + require.NoError(t, cfgFile.Unmarshal(&cfg)) + + assert.ErrorContains( + t, + cfg.(*internal.Config).Validate(), + "'token' must be set", + ) +} + +// TestConfigValidateMissingDataCenter verifies that +// the validation of a configuration file with +// the data center ID missing fails as expected. +func TestConfigValidateMissingDataCenter(t *testing.T) { + cfgFile := testutil.LoadConfigTestdata(t, "missing_dc") + + // Parse configuration. + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + require.NoError(t, cfgFile.Unmarshal(&cfg)) + + assert.ErrorContains( + t, + cfg.(*internal.Config).Validate(), + "'data_center' must be set", + ) +} + +// TestConfigValidateMissingDataCenter verifies that +// the validation of a configuration file with +// the collector name missing fails as expected. +func TestConfigValidateMissingCollectorName(t *testing.T) { + cfgFile := testutil.LoadConfigTestdata(t, "missing_collector_name") + + // Parse configuration. + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + require.NoError(t, cfgFile.Unmarshal(&cfg)) + + assert.ErrorContains( + t, + cfg.(*internal.Config).Validate(), + "'collector_name' must be set", + ) +} + +// TestConfigTokenRedacted checks that the configuration +// type doesn't leak its secret token unless it is accessed explicitly. +func TestConfigTokenRedacted(t *testing.T) { + cfg := &internal.Config{ + DataCenter: "eu-01", + IngestionToken: "SECRET", + } + // This is the only way of accessing the actual token. + require.Equal(t, "SECRET", string(cfg.IngestionToken)) + + // It is redacted when printed. + assert.Equal(t, "[REDACTED]", cfg.IngestionToken.String()) +} + +// TestConfigOTLPWithOverride converts configuration to +// OTLP gRPC Exporter configuration and verifies that overridden +// endpoint and token propagate correctly. +func TestConfigOTLPWithOverride(t *testing.T) { + cfgFile := testutil.LoadConfigTestdata(t, "url_override") + + // Parse configuration. + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + require.NoError(t, cfgFile.Unmarshal(&cfg)) + + // Convert it to the OTLP Exporter configuration. + otlpCfg, err := cfg.(*internal.Config).OTLPConfig() + require.NoError(t, err) + + // Verify that both the token and overridden URL were propagated + // to the OTLP configuration. + assert.Equal(t, "127.0.0.1:1234", otlpCfg.Endpoint) + assert.Equal( + t, + map[string]configopaque.String{"Authorization": "Bearer YOUR-INGESTION-TOKEN"}, + otlpCfg.Headers, + ) +} diff --git a/extension/solarwindsextension/go.mod b/extension/solarwindsextension/go.mod index c15d324..375d3a4 100644 --- a/extension/solarwindsextension/go.mod +++ b/extension/solarwindsextension/go.mod @@ -3,12 +3,13 @@ module github.com/solarwinds/solarwinds-otel-collector/extension/solarwindsexten go 1.23.4 require ( - github.com/stretchr/testify v1.9.0 + github.com/solarwinds/solarwinds-otel-collector/pkg/testutil v0.0.0-00010101000000-000000000000 + github.com/stretchr/testify v1.10.0 go.opentelemetry.io/collector/component v0.113.0 go.opentelemetry.io/collector/config/configgrpc v0.113.0 go.opentelemetry.io/collector/config/configopaque v1.19.0 go.opentelemetry.io/collector/config/configtls v1.19.0 - go.opentelemetry.io/collector/confmap v1.19.0 + go.opentelemetry.io/collector/confmap v1.21.0 go.opentelemetry.io/collector/exporter v0.113.0 go.opentelemetry.io/collector/exporter/otlpexporter v0.113.0 go.opentelemetry.io/collector/extension v0.113.0 @@ -31,7 +32,7 @@ require ( github.com/klauspost/compress v1.17.11 // indirect github.com/knadh/koanf/maps v0.1.1 // indirect github.com/knadh/koanf/providers/confmap v0.1.0 // indirect - github.com/knadh/koanf/v2 v2.1.1 // indirect + github.com/knadh/koanf/v2 v2.1.2 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect @@ -72,3 +73,5 @@ require ( google.golang.org/protobuf v1.35.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) + +replace github.com/solarwinds/solarwinds-otel-collector/pkg/testutil => ../../pkg/testutil diff --git a/extension/solarwindsextension/go.sum b/extension/solarwindsextension/go.sum index adf8e14..422c53a 100644 --- a/extension/solarwindsextension/go.sum +++ b/extension/solarwindsextension/go.sum @@ -31,8 +31,8 @@ github.com/knadh/koanf/maps v0.1.1 h1:G5TjmUh2D7G2YWf5SQQqSiHRJEjaicvU0KpypqB3NI github.com/knadh/koanf/maps v0.1.1/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI= github.com/knadh/koanf/providers/confmap v0.1.0 h1:gOkxhHkemwG4LezxxN8DMOFopOPghxRVp7JbIvdvqzU= github.com/knadh/koanf/providers/confmap v0.1.0/go.mod h1:2uLhxQzJnyHKfxG927awZC7+fyHFdQkd697K4MdLnIU= -github.com/knadh/koanf/v2 v2.1.1 h1:/R8eXqasSTsmDCsAyYj+81Wteg8AqrV9CP6gvsTsOmM= -github.com/knadh/koanf/v2 v2.1.1/go.mod h1:4mnTRbZCK+ALuBXHZMjDfG9y714L7TykVnZkXbMU3Es= +github.com/knadh/koanf/v2 v2.1.2 h1:I2rtLRqXRy1p01m/utEtpZSSA6dcJbgGVuE27kW2PzQ= +github.com/knadh/koanf/v2 v2.1.2/go.mod h1:Gphfaen0q1Fc1HTgJgSTC4oRX9R2R5ErYMZJy8fLJBo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -54,8 +54,8 @@ github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.opentelemetry.io/collector v0.113.0 h1:dBuo2/OKBhoMCR86W4fFJLXGQ0gJfKRmi65AZwFkU2I= @@ -82,8 +82,8 @@ go.opentelemetry.io/collector/config/configtls v1.19.0 h1:GQ/cF1hgNqHVBq2oSSrOFX go.opentelemetry.io/collector/config/configtls v1.19.0/go.mod h1:1hyqnYB3JqEUlk1ME/s9HYz4oCRcxQCRxsJitFFT/cA= go.opentelemetry.io/collector/config/internal v0.113.0 h1:9RAzH8v7ItFT1npHpvP0SvUzBHcZDliCGRo9Spp6v7c= go.opentelemetry.io/collector/config/internal v0.113.0/go.mod h1:yC7E4h1Uj0SubxcFImh6OvBHFTjMh99+A5PuyIgDWqc= -go.opentelemetry.io/collector/confmap v1.19.0 h1:TQ0lZpAKqgsE0EKk+u4JA+uBbPYeFRmWP3GH43w40CY= -go.opentelemetry.io/collector/confmap v1.19.0/go.mod h1:GgNu1ElPGmLn9govqIfjaopvdspw4PJ9KeDtWC4E2Q4= +go.opentelemetry.io/collector/confmap v1.21.0 h1:1tIcx2/Suwg8VhuPmQw87ba0ludPmumpFCFRZZa6RXA= +go.opentelemetry.io/collector/confmap v1.21.0/go.mod h1:Rrhs+MWoaP6AswZp+ReQ2VO9dfOfcUjdjiSHBsG+nec= go.opentelemetry.io/collector/consumer v0.113.0 h1:KJSiK5vSIY9dgPxwKfQ3gOgKtQsqc+7IB7mGhUAL5c8= go.opentelemetry.io/collector/consumer v0.113.0/go.mod h1:zHMlXYFaJlZoLCBR6UwWoyXZ/adcO1u2ydqUal3VmYU= go.opentelemetry.io/collector/consumer/consumererror v0.113.0 h1:Hd2N7n9RKbnKRaVrdw6fPBoQko5zZIgCxwVxkL6SAIE= diff --git a/extension/solarwindsextension/internal/datacell_test.go b/extension/solarwindsextension/internal/datacell_test.go new file mode 100644 index 0000000..9f56032 --- /dev/null +++ b/extension/solarwindsextension/internal/datacell_test.go @@ -0,0 +1,52 @@ +// Copyright 2024 SolarWinds Worldwide, LLC. All rights reserved. +// +// 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 internal + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// TestConfigValidateDataCenters verifies mappings +// for data centers (the mapping is case-insensitive). +func TestConfigValidateDataCenters(t *testing.T) { + type test struct { + dataCenter string + url string + ok bool + } + + tests := []test{ + {dataCenter: "na-01", url: "otel.collector.na-01.cloud.solarwinds.com:4317", ok: true}, + {dataCenter: "na-02", url: "otel.collector.na-02.cloud.solarwinds.com:4317", ok: true}, + {dataCenter: "eu-01", url: "otel.collector.eu-01.cloud.solarwinds.com:4317", ok: true}, + {dataCenter: "NA-01", url: "otel.collector.na-01.cloud.solarwinds.com:4317", ok: true}, + {dataCenter: "apj-01", url: "", ok: false}, + } + + for _, tc := range tests { + // Try to find a dataCenter URL for its ID. + url, err := lookupDataCenterURL(tc.dataCenter) + + if tc.ok { // A URL should be returned. + require.NoError(t, err) + assert.Equal(t, tc.url, url) + } else { // It should fail. + assert.ErrorContains(t, err, "unknown data center ID") + } + } +} diff --git a/extension/solarwindsextension/internal/hearthbeat_test.go b/extension/solarwindsextension/internal/hearthbeat_test.go index e9abe4f..07603cf 100644 --- a/extension/solarwindsextension/internal/hearthbeat_test.go +++ b/extension/solarwindsextension/internal/hearthbeat_test.go @@ -55,9 +55,11 @@ func (m *mockExporter) push(_ context.Context, metrics pmetric.Metrics) error { // in isolation with a mocked exporter and inspects // emitted metrics. func TestHeartbeatEmittingMetrics(t *testing.T) { - const testDuration = 1000 * time.Millisecond - const beatInterval = 100 * time.Millisecond - const expectedCount = int(testDuration / beatInterval) + const ( + testDuration = 1000 * time.Millisecond + beatInterval = 100 * time.Millisecond + expectedCount = int(testDuration / beatInterval) + ) mockExp := newMockExporter() hb := newHeartbeatWithExporter( @@ -86,7 +88,7 @@ func TestHeartbeatEmittingMetrics(t *testing.T) { expectedCount, len(mockExp.pushed), ) - + err = hb.Shutdown(ctx) require.NoError(t, err) } diff --git a/extension/solarwindsextension/testdata/full.yaml b/extension/solarwindsextension/testdata/full.yaml new file mode 100644 index 0000000..efa5f8d --- /dev/null +++ b/extension/solarwindsextension/testdata/full.yaml @@ -0,0 +1,4 @@ +token: "TOKEN" +data_center: "na-01" +collector_name: "best-collector-ever" +endpoint_url_override: "127.0.0.1:1234" diff --git a/extension/solarwindsextension/testdata/minimal.yaml b/extension/solarwindsextension/testdata/minimal.yaml new file mode 100644 index 0000000..e06a1eb --- /dev/null +++ b/extension/solarwindsextension/testdata/minimal.yaml @@ -0,0 +1,3 @@ +token: "YOUR-INGESTION-TOKEN" +data_center: "na-01" +collector_name: "mycoll" diff --git a/extension/solarwindsextension/testdata/missing_collector_name.yaml b/extension/solarwindsextension/testdata/missing_collector_name.yaml new file mode 100644 index 0000000..762db58 --- /dev/null +++ b/extension/solarwindsextension/testdata/missing_collector_name.yaml @@ -0,0 +1,2 @@ +token: "YOUR-INGESTION-TOKEN" +data_center: "na-01" diff --git a/extension/solarwindsextension/testdata/missing_dc.yaml b/extension/solarwindsextension/testdata/missing_dc.yaml new file mode 100644 index 0000000..31ac87a --- /dev/null +++ b/extension/solarwindsextension/testdata/missing_dc.yaml @@ -0,0 +1,2 @@ +token: "YOUR-INGESTION-TOKEN" +collector_name: "mycol" diff --git a/extension/solarwindsextension/testdata/missing_token.yaml b/extension/solarwindsextension/testdata/missing_token.yaml new file mode 100644 index 0000000..71cc90d --- /dev/null +++ b/extension/solarwindsextension/testdata/missing_token.yaml @@ -0,0 +1,2 @@ +data_center: "na-01" +collector_name: "mycol" diff --git a/extension/solarwindsextension/testdata/url_override.yaml b/extension/solarwindsextension/testdata/url_override.yaml new file mode 100644 index 0000000..15c6f70 --- /dev/null +++ b/extension/solarwindsextension/testdata/url_override.yaml @@ -0,0 +1,4 @@ +token: "YOUR-INGESTION-TOKEN" +data_center: "na-01" +collector_name: "mycol" +endpoint_url_override: "127.0.0.1:1234" diff --git a/pkg/testutil/config.go b/pkg/testutil/config.go new file mode 100644 index 0000000..48b0973 --- /dev/null +++ b/pkg/testutil/config.go @@ -0,0 +1,37 @@ +// Copyright 2024 SolarWinds Worldwide, LLC. All rights reserved. +// +// 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 testutil + +import ( + "fmt" + "path/filepath" + "testing" + + "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/confmap" + "go.opentelemetry.io/collector/confmap/confmaptest" +) + +// LoadConfigTestdata is a helper function to load a configuration +// testdata file by its name from the 'testdata' folder of the current package. +func LoadConfigTestdata(t *testing.T, name string) *confmap.Conf { + t.Helper() + + filename := fmt.Sprintf("%s.yaml", name) + cm, err := confmaptest.LoadConf(filepath.Join("./testdata", filename)) + require.NoError(t, err) + + return cm +} diff --git a/pkg/testutil/go.mod b/pkg/testutil/go.mod new file mode 100644 index 0000000..b10c5ba --- /dev/null +++ b/pkg/testutil/go.mod @@ -0,0 +1,21 @@ +module github.com/solarwinds/solarwinds-otel-collector/pkg/testutil + +go 1.23.4 +require ( + github.com/stretchr/testify v1.10.0 + go.opentelemetry.io/collector/confmap v1.21.0 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/go-viper/mapstructure/v2 v2.2.1 // indirect + github.com/knadh/koanf/maps v0.1.1 // indirect + github.com/knadh/koanf/providers/confmap v0.1.0 // indirect + github.com/knadh/koanf/v2 v2.1.2 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.27.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/pkg/testutil/go.sum b/pkg/testutil/go.sum new file mode 100644 index 0000000..da9779b --- /dev/null +++ b/pkg/testutil/go.sum @@ -0,0 +1,37 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= +github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/knadh/koanf/maps v0.1.1 h1:G5TjmUh2D7G2YWf5SQQqSiHRJEjaicvU0KpypqB3NIs= +github.com/knadh/koanf/maps v0.1.1/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI= +github.com/knadh/koanf/providers/confmap v0.1.0 h1:gOkxhHkemwG4LezxxN8DMOFopOPghxRVp7JbIvdvqzU= +github.com/knadh/koanf/providers/confmap v0.1.0/go.mod h1:2uLhxQzJnyHKfxG927awZC7+fyHFdQkd697K4MdLnIU= +github.com/knadh/koanf/v2 v2.1.2 h1:I2rtLRqXRy1p01m/utEtpZSSA6dcJbgGVuE27kW2PzQ= +github.com/knadh/koanf/v2 v2.1.2/go.mod h1:Gphfaen0q1Fc1HTgJgSTC4oRX9R2R5ErYMZJy8fLJBo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +go.opentelemetry.io/collector/confmap v1.21.0 h1:1tIcx2/Suwg8VhuPmQw87ba0ludPmumpFCFRZZa6RXA= +go.opentelemetry.io/collector/confmap v1.21.0/go.mod h1:Rrhs+MWoaP6AswZp+ReQ2VO9dfOfcUjdjiSHBsG+nec= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From dbfbc000cf461055ad789c557fa9bc8d44dad1ff Mon Sep 17 00:00:00 2001 From: Marek Simek Date: Mon, 16 Dec 2024 14:00:57 +0100 Subject: [PATCH 24/41] Fix Build --- cmd/solarwinds-otel-collector/go.mod | 4 +++- cmd/solarwinds-otel-collector/go.sum | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/cmd/solarwinds-otel-collector/go.mod b/cmd/solarwinds-otel-collector/go.mod index f5dcbd4..8bb517e 100644 --- a/cmd/solarwinds-otel-collector/go.mod +++ b/cmd/solarwinds-otel-collector/go.mod @@ -161,7 +161,7 @@ require ( github.com/solarwinds/solarwinds-otel-collector/exporter/solarwindsexporter v0.113.0 github.com/solarwinds/solarwinds-otel-collector/extension/solarwindsextension v0.113.0 go.opentelemetry.io/collector/component v0.113.0 - go.opentelemetry.io/collector/confmap v1.19.0 + go.opentelemetry.io/collector/confmap v1.21.0 go.opentelemetry.io/collector/confmap/provider/envprovider v1.19.0 go.opentelemetry.io/collector/confmap/provider/fileprovider v1.19.0 go.opentelemetry.io/collector/confmap/provider/httpprovider v1.19.0 @@ -713,3 +713,5 @@ require ( replace github.com/solarwinds/solarwinds-otel-collector/exporter/solarwindsexporter => ../../exporter/solarwindsexporter replace github.com/solarwinds/solarwinds-otel-collector/extension/solarwindsextension => ../../extension/solarwindsextension + +replace github.com/solarwinds/solarwinds-otel-collector/pkg/testutil => ../../pkg/testutil diff --git a/cmd/solarwinds-otel-collector/go.sum b/cmd/solarwinds-otel-collector/go.sum index 2f68302..d73c7c2 100644 --- a/cmd/solarwinds-otel-collector/go.sum +++ b/cmd/solarwinds-otel-collector/go.sum @@ -2542,8 +2542,8 @@ go.opentelemetry.io/collector/config/configtls v1.19.0 h1:GQ/cF1hgNqHVBq2oSSrOFX go.opentelemetry.io/collector/config/configtls v1.19.0/go.mod h1:1hyqnYB3JqEUlk1ME/s9HYz4oCRcxQCRxsJitFFT/cA= go.opentelemetry.io/collector/config/internal v0.114.0 h1:uWSDWTJb8T6xRjKD9/XmEARakXnxgYVYKUeId78hErc= go.opentelemetry.io/collector/config/internal v0.114.0/go.mod h1:yC7E4h1Uj0SubxcFImh6OvBHFTjMh99+A5PuyIgDWqc= -go.opentelemetry.io/collector/confmap v1.19.0 h1:TQ0lZpAKqgsE0EKk+u4JA+uBbPYeFRmWP3GH43w40CY= -go.opentelemetry.io/collector/confmap v1.19.0/go.mod h1:GgNu1ElPGmLn9govqIfjaopvdspw4PJ9KeDtWC4E2Q4= +go.opentelemetry.io/collector/confmap v1.21.0 h1:1tIcx2/Suwg8VhuPmQw87ba0ludPmumpFCFRZZa6RXA= +go.opentelemetry.io/collector/confmap v1.21.0/go.mod h1:Rrhs+MWoaP6AswZp+ReQ2VO9dfOfcUjdjiSHBsG+nec= go.opentelemetry.io/collector/confmap/provider/envprovider v1.19.0 h1:f8O/I5pVRN86Gx5mHekNx92S6fGdOS4VcooRJKWe6Bs= go.opentelemetry.io/collector/confmap/provider/envprovider v1.19.0/go.mod h1:AiaW5YW1LD0/WlZuc8eZuZPBH6PA9QqsiAYRX1iC6T0= go.opentelemetry.io/collector/confmap/provider/fileprovider v1.19.0 h1:TYwyk4ea3U+5MYcEjrzZAaonBcLlabQu8CZeB7ekAYY= From 6362c9e9b30a7ae6403c611503a59e4a9dad2cd8 Mon Sep 17 00:00:00 2001 From: Marek Simek Date: Mon, 16 Dec 2024 15:33:42 +0100 Subject: [PATCH 25/41] Fix Docstring --- extension/solarwindsextension/internal/config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extension/solarwindsextension/internal/config.go b/extension/solarwindsextension/internal/config.go index 6c05e55..7b3cec9 100644 --- a/extension/solarwindsextension/internal/config.go +++ b/extension/solarwindsextension/internal/config.go @@ -28,7 +28,7 @@ import ( "go.opentelemetry.io/collector/exporter/otlpexporter" ) -// Config represents a Solarwinds Exporter configuration. +// Config represents a Solarwinds Extension configuration. type Config struct { // DataCenter ID (e.g. na-01). DataCenter string `mapstructure:"data_center"` From a5dabb571c518809fa2ccdc9446f864c876e1280 Mon Sep 17 00:00:00 2001 From: Marek Simek Date: Mon, 16 Dec 2024 16:22:32 +0100 Subject: [PATCH 26/41] Expose 'insecure' Config Flag to Disable TLS in Tests --- exporter/solarwindsexporter/config.go | 6 ++++++ .../solarwindsexporter/solarwinds_exporter.go | 15 ++++++++++++++- exporter/solarwindsexporter/testdata/full.yaml | 1 - extension/solarwindsextension/config_test.go | 1 + extension/solarwindsextension/endpoint_config.go | 5 +++++ extension/solarwindsextension/extension.go | 6 +++--- extension/solarwindsextension/extension_test.go | 15 --------------- extension/solarwindsextension/internal/config.go | 14 ++++++++++++++ extension/solarwindsextension/testdata/full.yaml | 1 + 9 files changed, 44 insertions(+), 20 deletions(-) delete mode 100644 extension/solarwindsextension/extension_test.go diff --git a/exporter/solarwindsexporter/config.go b/exporter/solarwindsexporter/config.go index b4a3919..a374aff 100644 --- a/exporter/solarwindsexporter/config.go +++ b/exporter/solarwindsexporter/config.go @@ -44,6 +44,9 @@ type Config struct { ingestionToken configopaque.String `mapstructure:"-"` // endpointURL stores the URL provided by the Solarwinds Extension. endpointURL string `mapstructure:"-"` + // insecure stores the option to disable TLS provided + // by the Solarwinds Extension. + insecure bool `mapstructure:"-"` } // extensionAsComponent tries to parse `extension` value of the form 'type/name' @@ -110,6 +113,9 @@ func (cfg *Config) OTLPConfig() (*otlpexporter.Config, error) { ClientConfig: clientCfg, } + // Disable TLS for testing. + otlpConfig.ClientConfig.TLSSetting.Insecure = cfg.insecure + if err := otlpConfig.Validate(); err != nil { return nil, err } diff --git a/exporter/solarwindsexporter/solarwinds_exporter.go b/exporter/solarwindsexporter/solarwinds_exporter.go index 005e272..a0a986f 100644 --- a/exporter/solarwindsexporter/solarwinds_exporter.go +++ b/exporter/solarwindsexporter/solarwinds_exporter.go @@ -108,6 +108,10 @@ func (swiExporter *solarwindsExporter) initExporterType( } swiExporter.config.endpointURL = url + // Get TLS settings for testing. + insecure := endpointCfg.Insecure() + swiExporter.config.insecure = insecure + otlpExporter := otlpexporter.NewFactory() otlpCfg, err := swiExporter.config.OTLPConfig() if err != nil { @@ -130,7 +134,16 @@ func (swiExporter *solarwindsExporter) initExporterType( } -func findExtension(extensions map[component.ID]component.Component, cfgExtensionID *component.ID) *solarwindsextension.SolarwindsExtension { +// findExtension returns a found Solarwinds Extension or nil +// if not found. Respecting these rules: +// - If the name is provided and it's found, return it. +// - If no name is provided and there's only one Solarwinds Extension, +// return it. +// - Otherwise, return nil. +func findExtension( + extensions map[component.ID]component.Component, + cfgExtensionID *component.ID, +) *solarwindsextension.SolarwindsExtension { foundExtensions := make([]*solarwindsextension.SolarwindsExtension, 0) for foundExtensionID, ext := range extensions { diff --git a/exporter/solarwindsexporter/testdata/full.yaml b/exporter/solarwindsexporter/testdata/full.yaml index 103581b..8925e4c 100644 --- a/exporter/solarwindsexporter/testdata/full.yaml +++ b/exporter/solarwindsexporter/testdata/full.yaml @@ -11,4 +11,3 @@ retry_on_failure: multiplier: 2.4 max_interval: "40s" max_elapsed_time: "400s" - diff --git a/extension/solarwindsextension/config_test.go b/extension/solarwindsextension/config_test.go index 391088f..c9bc35a 100644 --- a/extension/solarwindsextension/config_test.go +++ b/extension/solarwindsextension/config_test.go @@ -42,6 +42,7 @@ func TestConfigUnmarshalFull(t *testing.T) { EndpointURLOverride: "127.0.0.1:1234", IngestionToken: "TOKEN", CollectorName: "best-collector-ever", + Insecure: true, }, cfg) } diff --git a/extension/solarwindsextension/endpoint_config.go b/extension/solarwindsextension/endpoint_config.go index aca02b3..c120c01 100644 --- a/extension/solarwindsextension/endpoint_config.go +++ b/extension/solarwindsextension/endpoint_config.go @@ -23,6 +23,7 @@ import ( type EndpointConfig interface { Url() (string, error) Token() configopaque.String + Insecure() bool } type endpointConfig struct{ cfg *internal.Config } @@ -40,3 +41,7 @@ func (c *endpointConfig) Url() (string, error) { func (c *endpointConfig) Token() configopaque.String { return c.cfg.IngestionToken } + +func (c *endpointConfig) Insecure() bool { + return c.cfg.Insecure +} diff --git a/extension/solarwindsextension/extension.go b/extension/solarwindsextension/extension.go index f7a790f..41045d4 100644 --- a/extension/solarwindsextension/extension.go +++ b/extension/solarwindsextension/extension.go @@ -31,7 +31,7 @@ type SolarwindsExtension struct { } func newExtension(ctx context.Context, set extension.Settings, cfg *internal.Config) (*SolarwindsExtension, error) { - set.Logger.Info("Creating SolarwindsExtension") + set.Logger.Info("Creating Solarwinds Extension") set.Logger.Info("Config", zap.Any("config", cfg)) e := &SolarwindsExtension{ @@ -50,12 +50,12 @@ func newExtension(ctx context.Context, set extension.Settings, cfg *internal.Con func (e *SolarwindsExtension) GetEndpointConfig() EndpointConfig { return newEndpointConfig(e.config) } func (e *SolarwindsExtension) Start(ctx context.Context, host component.Host) error { - e.logger.Info("Starting SolarwindsExtension") + e.logger.Info("Starting Solarwinds Extension") return e.heartbeat.Start(ctx, host) } func (e *SolarwindsExtension) Shutdown(ctx context.Context) error { - e.logger.Info("Shutting down SolarwindsExtension") + e.logger.Info("Shutting down Solarwinds Extension") // Everything must be shut down, regardless of the failure. return e.heartbeat.Shutdown(ctx) diff --git a/extension/solarwindsextension/extension_test.go b/extension/solarwindsextension/extension_test.go deleted file mode 100644 index 9bf5784..0000000 --- a/extension/solarwindsextension/extension_test.go +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright 2024 SolarWinds Worldwide, LLC. All rights reserved. -// -// 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 solarwindsextension diff --git a/extension/solarwindsextension/internal/config.go b/extension/solarwindsextension/internal/config.go index 7b3cec9..e7691f9 100644 --- a/extension/solarwindsextension/internal/config.go +++ b/extension/solarwindsextension/internal/config.go @@ -39,12 +39,17 @@ type Config struct { IngestionToken configopaque.String `mapstructure:"token"` // CollectorName is the name you will see in the SWO UI CollectorName string `mapstructure:"collector_name"` + // Insecure disables TLS in the exporters. + // Warning: Intended for testing use only + // together with the 'endpoint_url_override' option. + Insecure bool `mapstructure:"insecure"` } var ( missingDataCenterErr = errors.New("invalid configuration: 'data_center' must be set") missingTokenErr = errors.New("invalid configuration: 'token' must be set") missingCollectorNameErr = errors.New("invalid configuration: 'collector_name' must be set") + insecureInProdErr = errors.New("invalid configuration: 'insecure' is not allowed in production mode") ) // NewDefaultConfig creates a new default configuration. @@ -61,6 +66,10 @@ func (cfg *Config) Validate() error { return missingDataCenterErr } + if cfg.Insecure && cfg.EndpointURLOverride == "" { + return insecureInProdErr + } + if _, err := cfg.EndpointUrl(); err != nil { return fmt.Errorf("invalid 'data_center' value: %w", err) } @@ -103,6 +112,11 @@ func (cfg *Config) OTLPConfig() (*otlpexporter.Config, error) { }, } + // Disable TLS for testing. + if cfg.Insecure { + otlpConfig.ClientConfig.TLSSetting.Insecure = true + } + if err = otlpConfig.Validate(); err != nil { return nil, err } diff --git a/extension/solarwindsextension/testdata/full.yaml b/extension/solarwindsextension/testdata/full.yaml index efa5f8d..5604d15 100644 --- a/extension/solarwindsextension/testdata/full.yaml +++ b/extension/solarwindsextension/testdata/full.yaml @@ -2,3 +2,4 @@ token: "TOKEN" data_center: "na-01" collector_name: "best-collector-ever" endpoint_url_override: "127.0.0.1:1234" +insecure: true From eaeaf077cb8ec41bca13531fbd5c2724d05d5cc0 Mon Sep 17 00:00:00 2001 From: David Sevcik Date: Mon, 16 Dec 2024 18:49:09 +0100 Subject: [PATCH 27/41] Separating testing configuration --- extension/solarwindsextension/internal/config.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/extension/solarwindsextension/internal/config.go b/extension/solarwindsextension/internal/config.go index e7691f9..ae9036a 100644 --- a/extension/solarwindsextension/internal/config.go +++ b/extension/solarwindsextension/internal/config.go @@ -32,16 +32,17 @@ import ( type Config struct { // DataCenter ID (e.g. na-01). DataCenter string `mapstructure:"data_center"` - // EndpointURLOverride sets OTLP endpoint directly. - // Warning: Intended for testing use only, use `DataCenter` instead. - EndpointURLOverride string `mapstructure:"endpoint_url_override"` // IngestionToken is your secret generated SWO ingestion token. IngestionToken configopaque.String `mapstructure:"token"` // CollectorName is the name you will see in the SWO UI CollectorName string `mapstructure:"collector_name"` // Insecure disables TLS in the exporters. - // Warning: Intended for testing use only - // together with the 'endpoint_url_override' option. + + // ⚠️ Warning: For testing purpose only. + // EndpointURLOverride sets OTLP endpoint directly, it overrides the DataCenter configuration. + EndpointURLOverride string `mapstructure:"endpoint_url_override"` + // ⚠️ Warning: For testing purpose only. + // Insecure disables the TLS security. It can be used only together with EndpointURLOverride. Insecure bool `mapstructure:"insecure"` } From 324258404296d7c332b025fcaa72b8ae4e3e6b64 Mon Sep 17 00:00:00 2001 From: David Sevcik Date: Mon, 16 Dec 2024 18:51:14 +0100 Subject: [PATCH 28/41] Documentation --- CHANGELOG.md | 3 +++ README.md | 3 ++- exporter/solarwindsexporter/README.md | 6 ++++- extension/solarwindsextension/README.md | 30 +++++++++++++++++++++++++ 4 files changed, 40 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ee54498..7d4763e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +## v0.113.1 +Adds [SolarWinds Extension](./extension/solarwindsextension). The [SolarWinds Exporter](./exporter/solarwindsexporter) is now dependent on the extension. + ## v0.113.0 Initial version of SolarWinds OpenTelemetry Collector. The collector provides all available components (receivers, processors, exporters, connectors, providers) diff --git a/README.md b/README.md index e3fc7b6..bbdece3 100644 --- a/README.md +++ b/README.md @@ -12,12 +12,13 @@ The SolarWinds OpenTelemetry collector contains following components: - full set of [opentelemetry-collector processors](https://github.com/open-telemetry/opentelemetry-collector/tree/v0.113.0/processor) - full set of [opentelemetry-collector-contrib processors](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/v0.113.0/processor) - exporters + - [`solarwindsexporter`](./exporter/solarwindsexporter) - [`otlpexporter`](https://github.com/open-telemetry/opentelemetry-collector/tree/v0.113.0/exporter/otlpexporter) - [`fileexporter`](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/v0.113.0/exporter/fileexporter) - - [`solarwindsexporter`](./exporter/solarwindsexporter) - [`debugexporter`](https://github.com/open-telemetry/opentelemetry-collector/tree/v0.113.0/exporter/debugexporter) - [`nopexporter`](https://github.com/open-telemetry/opentelemetry-collector/tree/v0.113.0/exporter/nopexporter) - extensions + - [`solarwindsextension`](./extension/solarwindsextension) - full set of [opentelemetry-collector extensions](https://github.com/open-telemetry/opentelemetry-collector/tree/v0.113.0/extension) - full set of [opentelemetry-collector-contrib extensions](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/v0.113.0/extension) - connectors diff --git a/exporter/solarwindsexporter/README.md b/exporter/solarwindsexporter/README.md index b90c496..2c9dd36 100644 --- a/exporter/solarwindsexporter/README.md +++ b/exporter/solarwindsexporter/README.md @@ -17,7 +17,11 @@ You just need to include the SolarWinds Exporter in your exporter definitions an ```yaml exporters: + solarwinds: {} +extensions: solarwinds: + token: "TOKEN" + data_center: "na-01" ``` ## Full configuration @@ -45,7 +49,7 @@ extensions: data_center: "na-01" ``` > [!TIP] -> You can omit `extension` from the Solarwinds Exporter configuration above as there is only a single instance of the Solarwinds Extension. +> You can omit `extension` from the Solarwinds Exporter configuration above if there's only a single instance of the Solarwinds Extension. - `extension` (optional) - This name identifies an instance of the [Solarwinds Extension](../../extension/solarwindsextension) to be used by this exporter to obtain its configuration. If there is only a single instance of the extension, the configuration value is optional. The format mimics the identifier as it occurs in the collector configuration - diff --git a/extension/solarwindsextension/README.md b/extension/solarwindsextension/README.md index 10cfd17..a61fbc7 100644 --- a/extension/solarwindsextension/README.md +++ b/extension/solarwindsextension/README.md @@ -9,3 +9,33 @@ [development]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#development + +SolarWinds Extension is required sidecar for the [Solarwinds Exporter](../../exporter/solarwindsexporter). + +It provides these features: + +- Hearbeat signal + - It's a standard metric: `sw.otecol.uptime` + - The value is a time from the start of the collector in seconds + - This signal is necessary for SWO to detect the collector when installed and to determine if it's still alive. + - It also contains some additional informations as resource attributes for SWO: + - Collector name: `sw.collector.name` + +## Getting Started + +You just need to include the SolarWinds Extension in your extension definitions and provide the following configuration: + +```yaml +extensions: + solarwinds: + token: "YOUR-INGESTION-TOKEN" + data_center: "na-01" + collector_name: "Collector Display Name" +``` +- `token` (mandatory) - You can generate your token in your SWO (SolarWinds Observability) account under _Settings / API Tokens / Create API Token_. The type is "Ingestion". You can find the complete documentation [here](https://documentation.solarwinds.com/en/success_center/observability/content/settings/api-tokens.htm). +- `data_center` (mandatory) - Data center is the region you picked during the sign-up process. You can easily see in URLs after logging in to SolarWinds Observability SaaS - it's either `na-01`, `na-02` or `eu-01`. Please refer to the [documentation](https://documentation.solarwinds.com/en/success_center/observability/content/system_requirements/endpoints.htm#Find) for details. +- `collector_name` (mandatory) - this is the name under which the collector will be shown in the SWO UI. Doesn't have to be unique. + +## Development +- **Tests** can be executed with `make test`. +- After changes to `metadata.yaml` generated files need to be re-generated with `make generate`. The [mdatagen](http://go.opentelemetry.io/collector/cmd/mdatagen) tool has to be in the `PATH`. From 52e09d0ae55f850fdcbbb1a585436ab7b2e84ca5 Mon Sep 17 00:00:00 2001 From: David Sevcik Date: Mon, 16 Dec 2024 18:56:24 +0100 Subject: [PATCH 29/41] New line --- exporter/solarwindsexporter/config.go | 1 + 1 file changed, 1 insertion(+) diff --git a/exporter/solarwindsexporter/config.go b/exporter/solarwindsexporter/config.go index a374aff..e7de4dd 100644 --- a/exporter/solarwindsexporter/config.go +++ b/exporter/solarwindsexporter/config.go @@ -40,6 +40,7 @@ type Config struct { QueueSettings exporterhelper.QueueConfig `mapstructure:"sending_queue"` // Timeout configures timeout in the underlying OTLP exporter. Timeout exporterhelper.TimeoutConfig `mapstructure:"timeout,squash"` + // ingestionToken stores the token provided by the Solarwinds Extension. ingestionToken configopaque.String `mapstructure:"-"` // endpointURL stores the URL provided by the Solarwinds Extension. From 37baef7ed7960ac498c9544995ec59597246a36f Mon Sep 17 00:00:00 2001 From: David Sevcik Date: Mon, 16 Dec 2024 19:11:40 +0100 Subject: [PATCH 30/41] Error rename --- extension/solarwindsextension/internal/heartbeat.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extension/solarwindsextension/internal/heartbeat.go b/extension/solarwindsextension/internal/heartbeat.go index df295be..720ac29 100644 --- a/extension/solarwindsextension/internal/heartbeat.go +++ b/extension/solarwindsextension/internal/heartbeat.go @@ -50,7 +50,7 @@ type Heartbeat struct { beatInterval time.Duration } -var alreadyRunningError = errors.New("heartbeat already running") +var ErrAlreadyRunning = errors.New("heartbeat already running") func NewHeartbeat(ctx context.Context, set extension.Settings, cfg *Config) (*Heartbeat, error) { set.Logger.Debug("Creating Heartbeat") @@ -83,7 +83,7 @@ func (h *Heartbeat) Start(ctx context.Context, host component.Host) error { h.logger.Debug("Starting Heartbeat routine") if h.cancel != nil { - return alreadyRunningError + return ErrAlreadyRunning } err := h.exporter.start(ctx, host) From 413b882ddadc700ba7604ad8cbd28523c0fc742a Mon Sep 17 00:00:00 2001 From: Marek Simek Date: Tue, 17 Dec 2024 11:34:07 +0100 Subject: [PATCH 31/41] Use Standard Library Conventions for Error Naming --- Makefile.Common | 9 +++++++++ exporter/solarwindsexporter/Makefile | 8 +------- extension/solarwindsextension/Makefile | 7 +------ extension/solarwindsextension/internal/config.go | 16 ++++++++-------- 4 files changed, 19 insertions(+), 21 deletions(-) diff --git a/Makefile.Common b/Makefile.Common index 5576cd6..7da6b61 100644 --- a/Makefile.Common +++ b/Makefile.Common @@ -23,3 +23,12 @@ $(TOOLS_BIN_DIR): $(TOOLS_BIN_NAMES): $(TOOLS_BIN_DIR) $(TOOLS_MOD_DIR)/go.mod cd $(TOOLS_MOD_DIR) && $(GOCMD) build -o $@ -trimpath $(filter %/$(notdir $@),$(TOOLS_PKG_NAMES)) + +.PHONY: test +test: + go test ./... + +.PHONY: generate +generate: + go generate ./... + diff --git a/exporter/solarwindsexporter/Makefile b/exporter/solarwindsexporter/Makefile index 60d73c6..ded7a36 100644 --- a/exporter/solarwindsexporter/Makefile +++ b/exporter/solarwindsexporter/Makefile @@ -1,7 +1 @@ -.PHONY: test -test: - go test ./... - -.PHONY: generate -generate: - go generate ./... +include ../../Makefile.Common diff --git a/extension/solarwindsextension/Makefile b/extension/solarwindsextension/Makefile index 60d73c6..84677bc 100644 --- a/extension/solarwindsextension/Makefile +++ b/extension/solarwindsextension/Makefile @@ -1,7 +1,2 @@ -.PHONY: test -test: - go test ./... +include ../../Makefile.Common -.PHONY: generate -generate: - go generate ./... diff --git a/extension/solarwindsextension/internal/config.go b/extension/solarwindsextension/internal/config.go index ae9036a..843841f 100644 --- a/extension/solarwindsextension/internal/config.go +++ b/extension/solarwindsextension/internal/config.go @@ -47,10 +47,10 @@ type Config struct { } var ( - missingDataCenterErr = errors.New("invalid configuration: 'data_center' must be set") - missingTokenErr = errors.New("invalid configuration: 'token' must be set") - missingCollectorNameErr = errors.New("invalid configuration: 'collector_name' must be set") - insecureInProdErr = errors.New("invalid configuration: 'insecure' is not allowed in production mode") + ErrMissingDataCenter = errors.New("invalid configuration: 'data_center' must be set") + ErrMissingToken = errors.New("invalid configuration: 'token' must be set") + ErrMissingCollectorName = errors.New("invalid configuration: 'collector_name' must be set") + ErrInsecureInProd = errors.New("invalid configuration: 'insecure' is not allowed in production mode") ) // NewDefaultConfig creates a new default configuration. @@ -64,11 +64,11 @@ func NewDefaultConfig() component.Config { // Validate checks the configuration for its validity. func (cfg *Config) Validate() error { if cfg.DataCenter == "" && cfg.EndpointURLOverride == "" { - return missingDataCenterErr + return ErrMissingDataCenter } if cfg.Insecure && cfg.EndpointURLOverride == "" { - return insecureInProdErr + return ErrInsecureInProd } if _, err := cfg.EndpointUrl(); err != nil { @@ -76,10 +76,10 @@ func (cfg *Config) Validate() error { } if cfg.IngestionToken == "" { - return missingTokenErr + return ErrMissingToken } if cfg.CollectorName == "" { - return missingCollectorNameErr + return ErrMissingCollectorName } return nil From 53e7753aaf87cd430d131c5363ba430d54c7ad46 Mon Sep 17 00:00:00 2001 From: Marek Simek Date: Tue, 17 Dec 2024 13:44:29 +0100 Subject: [PATCH 32/41] Add Test for Insecure --- extension/solarwindsextension/config_test.go | 17 +++++++++++++++++ .../testdata/insecure_in_prod.yaml | 4 ++++ 2 files changed, 21 insertions(+) create mode 100644 extension/solarwindsextension/testdata/insecure_in_prod.yaml diff --git a/extension/solarwindsextension/config_test.go b/extension/solarwindsextension/config_test.go index c9bc35a..979fe7c 100644 --- a/extension/solarwindsextension/config_test.go +++ b/extension/solarwindsextension/config_test.go @@ -115,6 +115,23 @@ func TestConfigValidateMissingCollectorName(t *testing.T) { ) } +// TestConfigValidateInsecureInProd tests that 'insecure' +// cannot be enabled for a production endpoint. +func TestConfigValidateInsecureInProd(t *testing.T) { + cfgFile := testutil.LoadConfigTestdata(t, "insecure_in_prod") + + // Parse configuration. + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + require.NoError(t, cfgFile.Unmarshal(&cfg)) + + assert.ErrorContains( + t, + cfg.(*internal.Config).Validate(), + "invalid configuration: 'insecure'", + ) +} + // TestConfigTokenRedacted checks that the configuration // type doesn't leak its secret token unless it is accessed explicitly. func TestConfigTokenRedacted(t *testing.T) { diff --git a/extension/solarwindsextension/testdata/insecure_in_prod.yaml b/extension/solarwindsextension/testdata/insecure_in_prod.yaml new file mode 100644 index 0000000..0c0d906 --- /dev/null +++ b/extension/solarwindsextension/testdata/insecure_in_prod.yaml @@ -0,0 +1,4 @@ +token: "YOUR-INGESTION-TOKEN" +data_center: "na-02" +collector_name: "collector" +insecure: true From 1f6e7ea828e565b4754f7e514d410439e1ef59c7 Mon Sep 17 00:00:00 2001 From: Marek Simek Date: Tue, 17 Dec 2024 15:06:47 +0100 Subject: [PATCH 33/41] Validate Extension Name in Exporter --- exporter/solarwindsexporter/config.go | 10 ++++++++++ exporter/solarwindsexporter/config_test.go | 17 +++++++++++++++++ .../solarwindsexporter/testdata/invalid.yaml | 1 + 3 files changed, 28 insertions(+) create mode 100644 exporter/solarwindsexporter/testdata/invalid.yaml diff --git a/exporter/solarwindsexporter/config.go b/exporter/solarwindsexporter/config.go index e7de4dd..be26c97 100644 --- a/exporter/solarwindsexporter/config.go +++ b/exporter/solarwindsexporter/config.go @@ -83,6 +83,16 @@ func NewDefaultConfig() component.Config { // Validate checks the configuration for its validity. func (cfg *Config) Validate() error { + if len(cfg.Extension) != 0 { + _, err := cfg.extensionAsComponent() + if err != nil { + return fmt.Errorf( + "invalid configuration: %q is not a correct value for 'extension'", + cfg.Extension, + ) + } + } + return nil } diff --git a/exporter/solarwindsexporter/config_test.go b/exporter/solarwindsexporter/config_test.go index 9c48309..c923dcd 100644 --- a/exporter/solarwindsexporter/config_test.go +++ b/exporter/solarwindsexporter/config_test.go @@ -72,6 +72,23 @@ func TestConfigValidateOK(t *testing.T) { assert.NoError(t, cfg.(*Config).Validate()) } +// TestConfigValidateNOK. +func TestConfigValidateNOK(t *testing.T) { + cfgFile := testutil.LoadConfigTestdata(t, "invalid") + + // Parse configuration. + factory := NewFactory() + cfg := factory.CreateDefaultConfig() + require.NoError(t, cfgFile.Unmarshal(&cfg)) + + // Validation should fail with an error. + assert.ErrorContains( + t, + cfg.(*Config).Validate(), + "invalid configuration", + ) +} + // TestConfigTokenRedacted checks that the configuration // type doesn't leak its secret token unless it is accessed explicitly. func TestConfigTokenRedacted(t *testing.T) { diff --git a/exporter/solarwindsexporter/testdata/invalid.yaml b/exporter/solarwindsexporter/testdata/invalid.yaml new file mode 100644 index 0000000..abb2afa --- /dev/null +++ b/exporter/solarwindsexporter/testdata/invalid.yaml @@ -0,0 +1 @@ +extension: "/" From e6a91ea0b09c7ce7f1e23a04f2f7234e45cdd646 Mon Sep 17 00:00:00 2001 From: David Sevcik Date: Tue, 17 Dec 2024 15:55:54 +0100 Subject: [PATCH 34/41] Add comment --- exporter/solarwindsexporter/metadata.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/exporter/solarwindsexporter/metadata.yaml b/exporter/solarwindsexporter/metadata.yaml index d6602da..5b420e7 100644 --- a/exporter/solarwindsexporter/metadata.yaml +++ b/exporter/solarwindsexporter/metadata.yaml @@ -7,5 +7,6 @@ status: development: [traces, metrics, logs] tests: + # skipped because the exporter requires solarwindsextension to run skip_lifecycle: true From ff4c81691421cd9f86e32a6c909fd986742e753f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20=C5=A0ev=C4=8D=C3=ADk?= <63638505+david-sevcik@users.noreply.github.com> Date: Tue, 17 Dec 2024 15:58:05 +0100 Subject: [PATCH 35/41] Update extension/solarwindsextension/README.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Marek Šimek --- extension/solarwindsextension/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extension/solarwindsextension/README.md b/extension/solarwindsextension/README.md index a61fbc7..bb74c5d 100644 --- a/extension/solarwindsextension/README.md +++ b/extension/solarwindsextension/README.md @@ -14,7 +14,7 @@ SolarWinds Extension is required sidecar for the [Solarwinds Exporter](../../exp It provides these features: -- Hearbeat signal +- Heartbeat signal - It's a standard metric: `sw.otecol.uptime` - The value is a time from the start of the collector in seconds - This signal is necessary for SWO to detect the collector when installed and to determine if it's still alive. From a1c5372dc7282c21b07bc8cb1263cb64993c677c Mon Sep 17 00:00:00 2001 From: David Sevcik Date: Tue, 17 Dec 2024 16:50:33 +0100 Subject: [PATCH 36/41] Replace SWO with SolarWinds Observability SaaS --- extension/solarwindsextension/README.md | 8 ++++---- extension/solarwindsextension/internal/config.go | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/extension/solarwindsextension/README.md b/extension/solarwindsextension/README.md index bb74c5d..ee93516 100644 --- a/extension/solarwindsextension/README.md +++ b/extension/solarwindsextension/README.md @@ -17,8 +17,8 @@ It provides these features: - Heartbeat signal - It's a standard metric: `sw.otecol.uptime` - The value is a time from the start of the collector in seconds - - This signal is necessary for SWO to detect the collector when installed and to determine if it's still alive. - - It also contains some additional informations as resource attributes for SWO: + - This signal is necessary for SolarWinds Observability SaaS to detect the collector when installed and to determine if it's still alive. + - It also contains some additional informations as resource attributes for SolarWinds Observability SaaS: - Collector name: `sw.collector.name` ## Getting Started @@ -32,9 +32,9 @@ extensions: data_center: "na-01" collector_name: "Collector Display Name" ``` -- `token` (mandatory) - You can generate your token in your SWO (SolarWinds Observability) account under _Settings / API Tokens / Create API Token_. The type is "Ingestion". You can find the complete documentation [here](https://documentation.solarwinds.com/en/success_center/observability/content/settings/api-tokens.htm). +- `token` (mandatory) - You can generate your token in your SolarWinds Observability SaaS account under _Settings / API Tokens / Create API Token_. The type is "Ingestion". You can find the complete documentation [here](https://documentation.solarwinds.com/en/success_center/observability/content/settings/api-tokens.htm). - `data_center` (mandatory) - Data center is the region you picked during the sign-up process. You can easily see in URLs after logging in to SolarWinds Observability SaaS - it's either `na-01`, `na-02` or `eu-01`. Please refer to the [documentation](https://documentation.solarwinds.com/en/success_center/observability/content/system_requirements/endpoints.htm#Find) for details. -- `collector_name` (mandatory) - this is the name under which the collector will be shown in the SWO UI. Doesn't have to be unique. +- `collector_name` (mandatory) - The collector name passed in the heartbeat metric (as `sw.collector.name` resource attribute) to identify the collector. Doesn't have to be unique. ## Development - **Tests** can be executed with `make test`. diff --git a/extension/solarwindsextension/internal/config.go b/extension/solarwindsextension/internal/config.go index 843841f..82893aa 100644 --- a/extension/solarwindsextension/internal/config.go +++ b/extension/solarwindsextension/internal/config.go @@ -32,9 +32,9 @@ import ( type Config struct { // DataCenter ID (e.g. na-01). DataCenter string `mapstructure:"data_center"` - // IngestionToken is your secret generated SWO ingestion token. + // IngestionToken is your secret generated SolarWinds Observability SaaS ingestion token. IngestionToken configopaque.String `mapstructure:"token"` - // CollectorName is the name you will see in the SWO UI + // CollectorName name of the collector passed in the heartbeat metric CollectorName string `mapstructure:"collector_name"` // Insecure disables TLS in the exporters. From 5fd5d88692f412e9d0a600e53384059e4f2e772f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20=C5=A0ev=C4=8D=C3=ADk?= <63638505+david-sevcik@users.noreply.github.com> Date: Wed, 18 Dec 2024 10:28:37 +0100 Subject: [PATCH 37/41] Update exporter/solarwindsexporter/README.md --- exporter/solarwindsexporter/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exporter/solarwindsexporter/README.md b/exporter/solarwindsexporter/README.md index 2c9dd36..b671e88 100644 --- a/exporter/solarwindsexporter/README.md +++ b/exporter/solarwindsexporter/README.md @@ -17,7 +17,7 @@ You just need to include the SolarWinds Exporter in your exporter definitions an ```yaml exporters: - solarwinds: {} + solarwinds: extensions: solarwinds: token: "TOKEN" From 1f1e241f5ce49242a3ad7b6b7016c94b5256f7e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20=C5=A0ev=C4=8D=C3=ADk?= <63638505+david-sevcik@users.noreply.github.com> Date: Wed, 18 Dec 2024 10:43:47 +0100 Subject: [PATCH 38/41] Update README.md --- extension/solarwindsextension/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/extension/solarwindsextension/README.md b/extension/solarwindsextension/README.md index ee93516..45a46b1 100644 --- a/extension/solarwindsextension/README.md +++ b/extension/solarwindsextension/README.md @@ -10,10 +10,11 @@ [development]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#development -SolarWinds Extension is required sidecar for the [Solarwinds Exporter](../../exporter/solarwindsexporter). +The SolarWinds Extension offers capabilities related to the SolarWinds Observability SaaS platform and is required for the [SolarWinds Exporter](../../exporter/solarwindsexporter) to function. It provides these features: +- Endpoint configuratioin for the Solarwinds Exporters - Heartbeat signal - It's a standard metric: `sw.otecol.uptime` - The value is a time from the start of the collector in seconds From 81d5596b0bdd757e72a7cd9aa7bf63a730519f14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20=C5=A0ev=C4=8D=C3=ADk?= <63638505+david-sevcik@users.noreply.github.com> Date: Wed, 18 Dec 2024 10:47:03 +0100 Subject: [PATCH 39/41] Update README.md --- extension/solarwindsextension/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extension/solarwindsextension/README.md b/extension/solarwindsextension/README.md index 45a46b1..4702c24 100644 --- a/extension/solarwindsextension/README.md +++ b/extension/solarwindsextension/README.md @@ -14,7 +14,7 @@ The SolarWinds Extension offers capabilities related to the SolarWinds Observabi It provides these features: -- Endpoint configuratioin for the Solarwinds Exporters +- Endpoint configuration for the Solarwinds Exporters - Heartbeat signal - It's a standard metric: `sw.otecol.uptime` - The value is a time from the start of the collector in seconds From 0e3e6daec50150888ae7f652af348fb5dbb21fc3 Mon Sep 17 00:00:00 2001 From: Marek Simek Date: Wed, 18 Dec 2024 10:52:23 +0100 Subject: [PATCH 40/41] Downgrade confmap to Match Other OTEL Deps --- extension/solarwindsextension/README.md | 2 +- extension/solarwindsextension/go.mod | 2 +- extension/solarwindsextension/go.sum | 4 ++-- extension/solarwindsextension/testdata/full.yaml | 2 +- pkg/testutil/go.mod | 3 ++- pkg/testutil/go.sum | 4 ++-- 6 files changed, 9 insertions(+), 8 deletions(-) diff --git a/extension/solarwindsextension/README.md b/extension/solarwindsextension/README.md index 4702c24..9b84188 100644 --- a/extension/solarwindsextension/README.md +++ b/extension/solarwindsextension/README.md @@ -19,7 +19,7 @@ It provides these features: - It's a standard metric: `sw.otecol.uptime` - The value is a time from the start of the collector in seconds - This signal is necessary for SolarWinds Observability SaaS to detect the collector when installed and to determine if it's still alive. - - It also contains some additional informations as resource attributes for SolarWinds Observability SaaS: + - It also contains some additional information as resource attributes for SolarWinds Observability SaaS: - Collector name: `sw.collector.name` ## Getting Started diff --git a/extension/solarwindsextension/go.mod b/extension/solarwindsextension/go.mod index 375d3a4..8602072 100644 --- a/extension/solarwindsextension/go.mod +++ b/extension/solarwindsextension/go.mod @@ -9,7 +9,7 @@ require ( go.opentelemetry.io/collector/config/configgrpc v0.113.0 go.opentelemetry.io/collector/config/configopaque v1.19.0 go.opentelemetry.io/collector/config/configtls v1.19.0 - go.opentelemetry.io/collector/confmap v1.21.0 + go.opentelemetry.io/collector/confmap v1.19.0 go.opentelemetry.io/collector/exporter v0.113.0 go.opentelemetry.io/collector/exporter/otlpexporter v0.113.0 go.opentelemetry.io/collector/extension v0.113.0 diff --git a/extension/solarwindsextension/go.sum b/extension/solarwindsextension/go.sum index 422c53a..2ed1b3e 100644 --- a/extension/solarwindsextension/go.sum +++ b/extension/solarwindsextension/go.sum @@ -82,8 +82,8 @@ go.opentelemetry.io/collector/config/configtls v1.19.0 h1:GQ/cF1hgNqHVBq2oSSrOFX go.opentelemetry.io/collector/config/configtls v1.19.0/go.mod h1:1hyqnYB3JqEUlk1ME/s9HYz4oCRcxQCRxsJitFFT/cA= go.opentelemetry.io/collector/config/internal v0.113.0 h1:9RAzH8v7ItFT1npHpvP0SvUzBHcZDliCGRo9Spp6v7c= go.opentelemetry.io/collector/config/internal v0.113.0/go.mod h1:yC7E4h1Uj0SubxcFImh6OvBHFTjMh99+A5PuyIgDWqc= -go.opentelemetry.io/collector/confmap v1.21.0 h1:1tIcx2/Suwg8VhuPmQw87ba0ludPmumpFCFRZZa6RXA= -go.opentelemetry.io/collector/confmap v1.21.0/go.mod h1:Rrhs+MWoaP6AswZp+ReQ2VO9dfOfcUjdjiSHBsG+nec= +go.opentelemetry.io/collector/confmap v1.19.0 h1:TQ0lZpAKqgsE0EKk+u4JA+uBbPYeFRmWP3GH43w40CY= +go.opentelemetry.io/collector/confmap v1.19.0/go.mod h1:GgNu1ElPGmLn9govqIfjaopvdspw4PJ9KeDtWC4E2Q4= go.opentelemetry.io/collector/consumer v0.113.0 h1:KJSiK5vSIY9dgPxwKfQ3gOgKtQsqc+7IB7mGhUAL5c8= go.opentelemetry.io/collector/consumer v0.113.0/go.mod h1:zHMlXYFaJlZoLCBR6UwWoyXZ/adcO1u2ydqUal3VmYU= go.opentelemetry.io/collector/consumer/consumererror v0.113.0 h1:Hd2N7n9RKbnKRaVrdw6fPBoQko5zZIgCxwVxkL6SAIE= diff --git a/extension/solarwindsextension/testdata/full.yaml b/extension/solarwindsextension/testdata/full.yaml index 5604d15..b7bd8e3 100644 --- a/extension/solarwindsextension/testdata/full.yaml +++ b/extension/solarwindsextension/testdata/full.yaml @@ -1,5 +1,5 @@ token: "TOKEN" data_center: "na-01" -collector_name: "best-collector-ever" +collector_name: "test-collector" endpoint_url_override: "127.0.0.1:1234" insecure: true diff --git a/pkg/testutil/go.mod b/pkg/testutil/go.mod index b10c5ba..b262557 100644 --- a/pkg/testutil/go.mod +++ b/pkg/testutil/go.mod @@ -1,9 +1,10 @@ module github.com/solarwinds/solarwinds-otel-collector/pkg/testutil go 1.23.4 + require ( github.com/stretchr/testify v1.10.0 - go.opentelemetry.io/collector/confmap v1.21.0 + go.opentelemetry.io/collector/confmap v1.19.0 ) require ( diff --git a/pkg/testutil/go.sum b/pkg/testutil/go.sum index da9779b..a863f67 100644 --- a/pkg/testutil/go.sum +++ b/pkg/testutil/go.sum @@ -22,8 +22,8 @@ github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjR github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -go.opentelemetry.io/collector/confmap v1.21.0 h1:1tIcx2/Suwg8VhuPmQw87ba0ludPmumpFCFRZZa6RXA= -go.opentelemetry.io/collector/confmap v1.21.0/go.mod h1:Rrhs+MWoaP6AswZp+ReQ2VO9dfOfcUjdjiSHBsG+nec= +go.opentelemetry.io/collector/confmap v1.19.0 h1:TQ0lZpAKqgsE0EKk+u4JA+uBbPYeFRmWP3GH43w40CY= +go.opentelemetry.io/collector/confmap v1.19.0/go.mod h1:GgNu1ElPGmLn9govqIfjaopvdspw4PJ9KeDtWC4E2Q4= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= From dd69d6cf63f1f19d9238f170c634965d8521fd49 Mon Sep 17 00:00:00 2001 From: Marek Simek Date: Wed, 18 Dec 2024 11:07:56 +0100 Subject: [PATCH 41/41] Fix Test --- extension/solarwindsextension/config_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extension/solarwindsextension/config_test.go b/extension/solarwindsextension/config_test.go index 979fe7c..93da7b1 100644 --- a/extension/solarwindsextension/config_test.go +++ b/extension/solarwindsextension/config_test.go @@ -41,7 +41,7 @@ func TestConfigUnmarshalFull(t *testing.T) { DataCenter: "na-01", EndpointURLOverride: "127.0.0.1:1234", IngestionToken: "TOKEN", - CollectorName: "best-collector-ever", + CollectorName: "test-collector", Insecure: true, }, cfg) }