Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into feat/introduction-of-…
Browse files Browse the repository at this point in the history
…swohostmetrics-receiver
  • Loading branch information
jirkajanecek committed Jan 22, 2025
2 parents a18c37c + 5ec98e2 commit fdcfc0c
Show file tree
Hide file tree
Showing 10 changed files with 178 additions and 101 deletions.
1 change: 1 addition & 0 deletions extension/solarwindsextension/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ extensions:
- `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) - The collector name passed in the heartbeat metric (as `sw.otelcol.collector.name` resource attribute) to identify the collector. Doesn't have to be unique.
- `resource` (optional) - You can specify additional attributes to be added to the `sw.otecol.uptime` metric.
- `without_entity` (optional) - You can disable Collector entity creation in SolarWinds Observability by setting it to `true`. If not configured, entity creation is enabled by default.
## 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`.
1 change: 1 addition & 0 deletions extension/solarwindsextension/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ func TestConfigUnmarshalFull(t *testing.T) {
IngestionToken: "TOKEN",
CollectorName: "test-collector",
Resource: attributeMap,
WithoutEntity: true,
}, cfg)
}

Expand Down
1 change: 1 addition & 0 deletions extension/solarwindsextension/internal/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ type Config struct {
// EndpointURLOverride sets OTLP endpoint directly, it overrides the DataCenter configuration.
EndpointURLOverride string `mapstructure:"endpoint_url_override"`
Resource map[string]string `mapstructure:"resource"`
WithoutEntity bool `mapstructure:"without_entity"`
}

var (
Expand Down
7 changes: 7 additions & 0 deletions extension/solarwindsextension/internal/heartbeat.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ type Heartbeat struct {
metric *UptimeMetric
exporter MetricsExporter
collectorName string
withoutEntity bool

beatInterval time.Duration
resource map[string]string
Expand Down Expand Up @@ -78,6 +79,7 @@ func newHeartbeatWithExporter(
exporter: exporter,
beatInterval: defaultHeartbeatInterval,
resource: cfg.Resource,
withoutEntity: cfg.WithoutEntity,
}
}

Expand Down Expand Up @@ -165,6 +167,11 @@ func (h *Heartbeat) decorateResourceAttributes(resource pcommon.Resource) error
if h.collectorName != "" {
resource.Attributes().PutStr(CollectorNameAttribute, h.collectorName)
}
if h.withoutEntity {
resource.Attributes().PutStr("sw.otelcol.collector.entity_creation", "off")
} else {
resource.Attributes().PutStr("sw.otelcol.collector.entity_creation", "on")
}
resource.Attributes().PutStr("sw.otelcol.collector.version", version.Version)
return nil
}
1 change: 1 addition & 0 deletions extension/solarwindsextension/testdata/full.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ endpoint_url_override: "127.0.0.1:1234"
resource:
att1: "custom_attribute_value_1"
att2: "custom_attribute_value_2"
without_entity: true
4 changes: 4 additions & 0 deletions extension/solarwindsextension/testdata/without_entity.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
token: "YOUR-INGESTION-TOKEN"
data_center: "na-01"
collector_name: "test-collector"
without_entity: true
80 changes: 75 additions & 5 deletions internal/e2e/containers.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,17 @@ package e2e
import (
"context"
"errors"
"log"
"path/filepath"
"time"

"fmt"
"github.com/mdelapenya/tlscert"
"github.com/stretchr/testify/require"
"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/network"
"github.com/testcontainers/testcontainers-go/wait"
"log"
"path/filepath"
"strconv"
"testing"
"time"
)

const (
Expand Down Expand Up @@ -74,8 +78,9 @@ func runTestedSolarWindsOTELCollector(
ctx context.Context,
certDir string,
networkName string,
configName string,
) (testcontainers.Container, error) {
configPath, err := filepath.Abs(filepath.Join(".", "testdata", "emitting_collector.yaml"))
configPath, err := filepath.Abs(filepath.Join(".", "testdata", configName))
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -204,10 +209,75 @@ func runGeneratorContainer(
return container, err
}

// Starts the receiving collector, and the test collector.
// Test collector container is started with the supplied config file to be tested.
// Returns the receiving collector container instance, so tests can check the ingested data is as expected.
func startCollectorContainers(
t *testing.T,
ctx context.Context,
config string,
signalType SignalType,
waitTime time.Duration,
) testcontainers.Container {

net, err := network.New(ctx)
require.NoError(t, err)
testcontainers.CleanupNetwork(t, net)

certPath := t.TempDir()
_, err = generateCertificates(receivingContainer, certPath)
require.NoError(t, err)

rContainer, err := runReceivingSolarWindsOTELCollector(ctx, certPath, net.Name)
require.NoError(t, err)
testcontainers.CleanupContainer(t, rContainer)

eContainer, err := runTestedSolarWindsOTELCollector(ctx, certPath, net.Name, config)
require.NoError(t, err)
testcontainers.CleanupContainer(t, eContainer)

cmd := []string{
signalType.String(),
fmt.Sprintf("--%s", signalType), strconv.Itoa(samplesCount),
"--otlp-insecure",
"--otlp-endpoint", fmt.Sprintf("%s:%d", testedContainer, port),
"--otlp-attributes", fmt.Sprintf("%s=\"%s\"", resourceAttributeName, resourceAttributeValue),
}

gContainer, err := runGeneratorContainer(ctx, net.Name, cmd)
require.NoError(t, err)
testcontainers.CleanupContainer(t, gContainer)

<-time.After(waitTime)

return rContainer
}

type logConsumer struct {
Prefix string
}

func (lc *logConsumer) Accept(l testcontainers.Log) {
log.Printf("***%s: %s", lc.Prefix, string(l.Content))
}

type SignalType int

const (
Logs SignalType = iota
Metrics
Traces
)

func (s SignalType) String() string {
switch s {
case Logs:
return "logs"
case Metrics:
return "metrics"
case Traces:
return "traces"
default:
panic("unexpected signal type")
}
}
103 changes: 7 additions & 96 deletions internal/e2e/signals_processing_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,14 @@ package e2e

import (
"context"
"fmt"
"io"
"log"
"strconv"
"strings"
"testing"
"time"

"github.com/solarwinds/solarwinds-otel-collector/pkg/version"
"github.com/stretchr/testify/require"
"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/network"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/pdata/pmetric"
Expand All @@ -45,72 +41,13 @@ const (

func TestMetricStream(t *testing.T) {
ctx := context.Background()

net, err := network.New(ctx)
require.NoError(t, err)
testcontainers.CleanupNetwork(t, net)

certPath := t.TempDir()
_, err = generateCertificates(receivingContainer, certPath)
require.NoError(t, err)

rContainer, err := runReceivingSolarWindsOTELCollector(ctx, certPath, net.Name)
require.NoError(t, err)
testcontainers.CleanupContainer(t, rContainer)

eContainer, err := runTestedSolarWindsOTELCollector(ctx, certPath, net.Name)
require.NoError(t, err)
testcontainers.CleanupContainer(t, eContainer)

cmd := []string{
"metrics",
"--metrics", strconv.Itoa(samplesCount),
"--otlp-insecure",
"--otlp-endpoint", fmt.Sprintf("%s:%d", testedContainer, port),
"--otlp-attributes", fmt.Sprintf("%s=\"%s\"", resourceAttributeName, resourceAttributeValue),
}

gContainer, err := runGeneratorContainer(ctx, net.Name, cmd)
require.NoError(t, err)
testcontainers.CleanupContainer(t, gContainer)

<-time.After(collectorRunningPeriod)

rContainer := startCollectorContainers(t, ctx, "emitting_collector.yaml", Metrics, collectorRunningPeriod)
evaluateMetricsStream(t, ctx, rContainer, samplesCount)
}

func TestTracesStream(t *testing.T) {
ctx := context.Background()

net, err := network.New(ctx)
require.NoError(t, err)
testcontainers.CleanupNetwork(t, net)

certPath := t.TempDir()
_, err = generateCertificates(receivingContainer, certPath)
require.NoError(t, err)

rContainer, err := runReceivingSolarWindsOTELCollector(ctx, certPath, net.Name)
require.NoError(t, err)
testcontainers.CleanupContainer(t, rContainer)

eContainer, err := runTestedSolarWindsOTELCollector(ctx, certPath, net.Name)
require.NoError(t, err)
testcontainers.CleanupContainer(t, eContainer)

cmd := []string{
"traces",
"--traces", strconv.Itoa(samplesCount),
"--otlp-insecure",
"--otlp-endpoint", fmt.Sprintf("%s:%d", testedContainer, port),
"--otlp-attributes", fmt.Sprintf("%s=\"%s\"", resourceAttributeName, resourceAttributeValue),
}

gContainer, err := runGeneratorContainer(ctx, net.Name, cmd)
require.NoError(t, err)
testcontainers.CleanupContainer(t, gContainer)

<-time.After(collectorRunningPeriod)
rContainer := startCollectorContainers(t, ctx, "emitting_collector.yaml", Traces, collectorRunningPeriod)

// Traces coming in couples.
expectedTracesCount := samplesCount * 2
Expand All @@ -119,37 +56,7 @@ func TestTracesStream(t *testing.T) {

func TestLogsStream(t *testing.T) {
ctx := context.Background()

net, err := network.New(ctx)
require.NoError(t, err)
testcontainers.CleanupNetwork(t, net)

certPath := t.TempDir()
_, err = generateCertificates(receivingContainer, certPath)
require.NoError(t, err)

rContainer, err := runReceivingSolarWindsOTELCollector(ctx, certPath, net.Name)
require.NoError(t, err)
testcontainers.CleanupContainer(t, rContainer)

eContainer, err := runTestedSolarWindsOTELCollector(ctx, certPath, net.Name)
require.NoError(t, err)
testcontainers.CleanupContainer(t, eContainer)

cmd := []string{
"logs",
"--logs", strconv.Itoa(samplesCount),
"--otlp-insecure",
"--otlp-endpoint", fmt.Sprintf("%s:%d", testedContainer, port),
"--otlp-attributes", fmt.Sprintf("%s=\"%s\"", resourceAttributeName, resourceAttributeValue),
}

gContainer, err := runGeneratorContainer(ctx, net.Name, cmd)
require.NoError(t, err)
testcontainers.CleanupContainer(t, gContainer)

<-time.After(collectorRunningPeriod)

rContainer := startCollectorContainers(t, ctx, "emitting_collector.yaml", Logs, collectorRunningPeriod)
evaluateLogsStream(t, ctx, rContainer, samplesCount)
}

Expand Down Expand Up @@ -283,6 +190,10 @@ func evaluateHeartbeatMetric(
v2, available2 := atts.Get("custom_attribute")
require.True(t, available2, "custom_attribute resource attribute must be available")
require.Equal(t, "custom_attribute_value", v2.AsString(), "attribute value must be the same")

v3, available3 := atts.Get("sw.otelcol.collector.entity_creation")
require.True(t, available3, "sw.otelcol.collector.entity_creation resource attribute must be available")
require.Equal(t, "on", v3.AsString(), "attribute value must be the same")
}

func evaluateResourceAttributes(
Expand Down
28 changes: 28 additions & 0 deletions internal/e2e/testdata/emitting_collector_without_entity.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
service:
extensions: [solarwinds]
pipelines:
metrics:
receivers: [otlp]
exporters: [solarwinds]
traces:
receivers: [otlp]
exporters: [solarwinds]
logs:
receivers: [otlp]
exporters: [solarwinds]

receivers:
otlp:
protocols:
grpc:
endpoint: :17016

extensions:
solarwinds:
token: <no-matter-in-test>
collector_name: "testing_collector_name"
endpoint_url_override: receiver:17016
without_entity: true

exporters:
solarwinds:
53 changes: 53 additions & 0 deletions internal/e2e/without_entity_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Copyright 2025 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:build e2e

package e2e

import (
"context"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/pdata/pmetric"
"testing"
)

func TestWithoutEntity(t *testing.T) {
ctx := context.Background()
rContainer := startCollectorContainers(t, ctx, "emitting_collector_without_entity.yaml", Metrics, collectorRunningPeriod)
ms := pmetric.NewMetrics()
mum := new(pmetric.JSONUnmarshaler)
lines, _ := loadResultFile(ctx, rContainer, "/tmp/result.json")
for _, line := range lines {
// Metrics to process.
m, err := mum.UnmarshalMetrics([]byte(line))
if err == nil && m.ResourceMetrics().Len() != 0 {
m.ResourceMetrics().MoveAndAppendTo(ms.ResourceMetrics())
continue
}
}
evaluateHeartbeatMetricHasEntityCreationAsOff(t, ms)
}

func evaluateHeartbeatMetricHasEntityCreationAsOff(
t *testing.T,
ms pmetric.Metrics,
) {
require.GreaterOrEqual(t, ms.ResourceMetrics().Len(), 1, "there must be at least one metric")
atts := ms.ResourceMetrics().At(0).Resource().Attributes()

v, available := atts.Get("sw.otelcol.collector.entity_creation")
require.True(t, available, "sw.otelcol.collector.entity_creation resource attribute must be available")
require.Equal(t, "off", v.AsString(), "attribute value must be the same")
}

0 comments on commit fdcfc0c

Please sign in to comment.