Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add lifecycle tests to all default components #2741

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
211 changes: 211 additions & 0 deletions service/defaultcomponents/default_exporters_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package defaultcomponents

import (
"context"
"errors"
"io/ioutil"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/zap"

"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/config/configerror"
"go.opentelemetry.io/collector/config/configgrpc"
"go.opentelemetry.io/collector/config/configmodels"
"go.opentelemetry.io/collector/exporter/fileexporter"
"go.opentelemetry.io/collector/exporter/jaegerexporter"
"go.opentelemetry.io/collector/exporter/kafkaexporter"
"go.opentelemetry.io/collector/exporter/opencensusexporter"
"go.opentelemetry.io/collector/exporter/otlpexporter"
"go.opentelemetry.io/collector/exporter/otlphttpexporter"
"go.opentelemetry.io/collector/exporter/prometheusexporter"
"go.opentelemetry.io/collector/exporter/zipkinexporter"
"go.opentelemetry.io/collector/testutil"
)

func TestDefaultExporters(t *testing.T) {
factories, err := Components()
assert.NoError(t, err)

expFactories := factories.Exporters
endpoint := testutil.GetAvailableLocalAddress(t)

tests := []struct {
exporter configmodels.Type
getConfigFn getExporterConfigFn
}{
{
exporter: "file",
getConfigFn: func() configmodels.Exporter {
cfg := expFactories["file"].CreateDefaultConfig().(*fileexporter.Config)
f, err := ioutil.TempFile("", "otelcol_defaults_file_exporter_test*.tmp")
require.NoError(t, err)
assert.NoError(t, f.Close())
cfg.Path = f.Name()
return cfg
},
},
{
exporter: "jaeger",
getConfigFn: func() configmodels.Exporter {
cfg := expFactories["jaeger"].CreateDefaultConfig().(*jaegerexporter.Config)
cfg.Endpoint = endpoint
return cfg
},
},
{
exporter: "kafka",
getConfigFn: func() configmodels.Exporter {
cfg := expFactories["kafka"].CreateDefaultConfig().(*kafkaexporter.Config)
cfg.Brokers = []string{"invalid:9092"}
// this disables contacting the broker so we can successfully create the exporter
cfg.Metadata.Full = false
return cfg
},
},
{
exporter: "logging",
},
{
exporter: "opencensus",
getConfigFn: func() configmodels.Exporter {
cfg := expFactories["opencensus"].CreateDefaultConfig().(*opencensusexporter.Config)
cfg.GRPCClientSettings = configgrpc.GRPCClientSettings{
Endpoint: endpoint,
}
return cfg
},
},
{
exporter: "otlp",
getConfigFn: func() configmodels.Exporter {
cfg := expFactories["otlp"].CreateDefaultConfig().(*otlpexporter.Config)
cfg.GRPCClientSettings = configgrpc.GRPCClientSettings{
Endpoint: endpoint,
}
return cfg
},
},
{
exporter: "otlphttp",
getConfigFn: func() configmodels.Exporter {
cfg := expFactories["otlphttp"].CreateDefaultConfig().(*otlphttpexporter.Config)
cfg.Endpoint = "http://" + endpoint
return cfg
},
},
{
exporter: "prometheus",
getConfigFn: func() configmodels.Exporter {
cfg := expFactories["prometheus"].CreateDefaultConfig().(*prometheusexporter.Config)
cfg.Endpoint = endpoint
return cfg
},
},
{
exporter: "prometheusremotewrite",
},
{
exporter: "zipkin",
getConfigFn: func() configmodels.Exporter {
cfg := expFactories["zipkin"].CreateDefaultConfig().(*zipkinexporter.Config)
cfg.Endpoint = endpoint
return cfg
},
},
}

assert.Equal(t, len(tests), len(expFactories))
for _, tt := range tests {
t.Run(string(tt.exporter), func(t *testing.T) {
factory, ok := expFactories[tt.exporter]
require.True(t, ok)
assert.Equal(t, tt.exporter, factory.Type())
assert.Equal(t, tt.exporter, factory.CreateDefaultConfig().Type())

verifyExporterLifecycle(t, factory, tt.getConfigFn)
})
}
}

// GetExporterConfigFn is used customize the configuration passed to the verification.
// This is used to change ports or provide values required but not provided by the
// default configuration.
type getExporterConfigFn func() configmodels.Exporter

// verifyExporterLifecycle is used to test if an exporter type can handle the typical
// lifecycle of a component. The getConfigFn parameter only need to be specified if
// the test can't be done with the default configuration for the component.
func verifyExporterLifecycle(t *testing.T, factory component.ExporterFactory, getConfigFn getExporterConfigFn) {
ctx := context.Background()
host := newAssertNoErrorHost(t)
expCreateParams := component.ExporterCreateParams{
Logger: zap.NewNop(),
ApplicationStartInfo: component.DefaultApplicationStartInfo(),
}

if getConfigFn == nil {
getConfigFn = factory.CreateDefaultConfig
}

createFns := []createExporterFn{
wrapCreateLogsExp(factory),
wrapCreateTracesExp(factory),
wrapCreateMetricsExp(factory),
}

for _, createFn := range createFns {
firstExp, err := createFn(ctx, expCreateParams, getConfigFn())
if errors.Is(err, configerror.ErrDataTypeIsNotSupported) {
continue
}
require.NoError(t, err)
require.NoError(t, firstExp.Start(ctx, host))
require.NoError(t, firstExp.Shutdown(ctx))

secondExp, err := createFn(ctx, expCreateParams, getConfigFn())
require.NoError(t, err)
require.NoError(t, secondExp.Start(ctx, host))
require.NoError(t, secondExp.Shutdown(ctx))
}
}

type createExporterFn func(
ctx context.Context,
params component.ExporterCreateParams,
cfg configmodels.Exporter,
) (component.Exporter, error)

func wrapCreateLogsExp(factory component.ExporterFactory) createExporterFn {
return func(ctx context.Context, params component.ExporterCreateParams, cfg configmodels.Exporter) (component.Exporter, error) {
return factory.CreateLogsExporter(ctx, params, cfg)
}
}

func wrapCreateTracesExp(factory component.ExporterFactory) createExporterFn {
return func(ctx context.Context, params component.ExporterCreateParams, cfg configmodels.Exporter) (component.Exporter, error) {
return factory.CreateTracesExporter(ctx, params, cfg)
}
}

func wrapCreateMetricsExp(factory component.ExporterFactory) createExporterFn {
return func(ctx context.Context, params component.ExporterCreateParams, cfg configmodels.Exporter) (component.Exporter, error) {
return factory.CreateMetricsExporter(ctx, params, cfg)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.

// Program otelcol is the OpenTelemetry Collector that collects stats
// and traces and exports to a configured backend.
package defaultcomponents

import (
Expand All @@ -29,85 +27,40 @@ import (
"go.opentelemetry.io/collector/config/configmodels"
)

func TestDefaultComponents(t *testing.T) {
expectedExtensions := []configmodels.Type{
"health_check",
"pprof",
"zpages",
"fluentbit",
}
expectedReceivers := []configmodels.Type{
"jaeger",
"zipkin",
"prometheus",
"opencensus",
"otlp",
"hostmetrics",
"fluentforward",
"kafka",
}
expectedProcessors := []configmodels.Type{
"attributes",
"resource",
"batch",
"memory_limiter",
"probabilistic_sampler",
"span",
"filter",
}
expectedExporters := []configmodels.Type{
"opencensus",
"prometheus",
"prometheusremotewrite",
"logging",
"zipkin",
"jaeger",
"file",
"otlp",
"otlphttp",
"kafka",
}

factories, err := Components()
assert.NoError(t, err)

exts := factories.Extensions
assert.Equal(t, len(expectedExtensions), len(exts))
for _, k := range expectedExtensions {
v, ok := exts[k]
assert.True(t, ok)
assert.Equal(t, k, v.Type())
cfg := v.CreateDefaultConfig()
assert.Equal(t, k, cfg.Type())

verifyExtensionLifecycle(t, v, nil)
}
func TestDefaultExtensions(t *testing.T) {
allFactories, err := Components()
require.NoError(t, err)

recvs := factories.Receivers
assert.Equal(t, len(expectedReceivers), len(recvs))
for _, k := range expectedReceivers {
v, ok := recvs[k]
require.True(t, ok)
assert.Equal(t, k, v.Type())
assert.Equal(t, k, v.CreateDefaultConfig().Type())
extFactories := allFactories.Extensions

tests := []struct {
extension configmodels.Type
getConfigFn getExtensionConfigFn
}{
{
extension: "health_check",
},
{
extension: "pprof",
},
{
extension: "zpages",
},
{
extension: "fluentbit",
},
}

procs := factories.Processors
assert.Equal(t, len(expectedProcessors), len(procs))
for _, k := range expectedProcessors {
v, ok := procs[k]
require.True(t, ok)
assert.Equal(t, k, v.Type())
assert.Equal(t, k, v.CreateDefaultConfig().Type())
}
assert.Equal(t, len(tests), len(extFactories))
for _, tt := range tests {
t.Run(string(tt.extension), func(t *testing.T) {
factory, ok := extFactories[tt.extension]
require.True(t, ok)
assert.Equal(t, tt.extension, factory.Type())
assert.Equal(t, tt.extension, factory.CreateDefaultConfig().Type())

exps := factories.Exporters
assert.Equal(t, len(expectedExporters), len(exps))
for _, k := range expectedExporters {
v, ok := exps[k]
require.True(t, ok)
assert.Equal(t, k, v.Type())
assert.Equal(t, k, v.CreateDefaultConfig().Type())
verifyExtensionLifecycle(t, factory, nil)
})
}
}

Expand All @@ -134,13 +87,12 @@ func verifyExtensionLifecycle(t *testing.T, factory component.ExtensionFactory,
firstExt, err := factory.CreateExtension(ctx, extCreateParams, getConfigFn())
require.NoError(t, err)
require.NoError(t, firstExt.Start(ctx, host))
require.NoError(t, firstExt.Shutdown(ctx))

secondExt, err := factory.CreateExtension(ctx, extCreateParams, getConfigFn())
assert.NoError(t, err)

assert.NoError(t, firstExt.Shutdown(ctx))
assert.NoError(t, secondExt.Start(ctx, host))
assert.NoError(t, secondExt.Shutdown(ctx))
require.NoError(t, err)
require.NoError(t, secondExt.Start(ctx, host))
require.NoError(t, secondExt.Shutdown(ctx))
}

// assertNoErrorHost implements a component.Host that asserts that there were no errors.
Expand Down
Loading