Skip to content

Commit

Permalink
Added routing exporter
Browse files Browse the repository at this point in the history
Closes #1260

Signed-off-by: Juraci Paixão Kröhling <[email protected]>
  • Loading branch information
jpkrohling committed Aug 21, 2020
1 parent 1e65674 commit 4115bf6
Show file tree
Hide file tree
Showing 8 changed files with 1,000 additions and 0 deletions.
46 changes: 46 additions & 0 deletions exporter/routingexporter/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// 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
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package routingexporter

import (
"go.opentelemetry.io/collector/config/configmodels"
)

// Config defines configuration for the Routing exporter.
type Config struct {
configmodels.ExporterSettings `mapstructure:",squash"`

// DefaultExporters contains the list of exporters to use when a more specific record can't be found in the routing table. Optional.
DefaultExporters []string `mapstructure:"default_exporters"`

// FromAttribute contains the attribute name to look up the tenant value. This attribute should be part of the context propagated
// down from the previous receivers and/or processors. If all the receivers and processors are propagating the entire context correctly,
// this could be the HTTP/gRPC header from the original request/RPC. Typically, aggregation processors (batch, queued_retry, groupbytrace)
// will create a new context, so, those should be avoided when using this exporter. If you need those capabilities, add them as
// part of the table's pipeline. Although the HTTP spec allows headers to be repeated, this exporter will only use the first value. Required.
FromAttribute string `mapstructure:"from_attribute"`

// Table contains the routing table for this exporter. Required.
Table []RoutingTableItem `mapstructure:"table"`
}

// RoutingTableItem specifies how data should be routed to the different exporters
type RoutingTableItem struct {
// Value represents a possible value for the field specified under FromAttribute. Required.
Value string `mapstructure:"value"`

// Exporters contains the list of exporters to use when the value from the FromAttribute field matches this table item. When no exporters are specified, the ones specified under DefaultExporters are used, if any. Optional.
Exporters []string `mapstructure:"exporters"`
}
67 changes: 67 additions & 0 deletions exporter/routingexporter/config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// 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
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package routingexporter

import (
"path"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/config/configmodels"
"go.opentelemetry.io/collector/config/configtest"
"go.opentelemetry.io/collector/exporter/jaegerexporter"
"go.opentelemetry.io/collector/exporter/otlpexporter"
)

func TestLoadConfig(t *testing.T) {
factories, err := componenttest.ExampleComponents()
assert.NoError(t, err)

factory := NewFactory()
factories.Exporters[typeStr] = factory

// we don't need to use them in this test, but the config has them
factories.Exporters["otlp"] = otlpexporter.NewFactory()
factories.Exporters["jaeger"] = jaegerexporter.NewFactory()

cfg, err := configtest.LoadConfigFile(t, path.Join(".", "testdata", "config.yaml"), factories)

require.NoError(t, err)
require.NotNil(t, cfg)

parsed := cfg.Exporters["routing"]
assert.Equal(t, parsed,
&Config{
ExporterSettings: configmodels.ExporterSettings{
NameVal: "routing",
TypeVal: "routing",
},
DefaultExporters: []string{"otlp"},
FromAttribute: "X-Tenant",
Table: []RoutingTableItem{
{
Value: "acme",
Exporters: []string{"jaeger/acme", "otlp/acme"},
},
{
Value: "globex",
Exporters: []string{"otlp/globex"},
},
},
})
}
55 changes: 55 additions & 0 deletions exporter/routingexporter/factory.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// 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
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package routingexporter

import (
"context"

"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/config/configmodels"
"go.opentelemetry.io/collector/exporter/exporterhelper"
)

const (
// The value of "type" key in configuration.
typeStr = "routing"
)

// NewFactory creates a factory for OTLP exporter.
func NewFactory() component.ExporterFactory {
return exporterhelper.NewFactory(
typeStr,
createDefaultConfig,
exporterhelper.WithTraces(createTraceExporter),
)
}

func createDefaultConfig() configmodels.Exporter {
return &Config{}
}

func createTraceExporter(_ context.Context, params component.ExporterCreateParams, cfg configmodels.Exporter) (component.TraceExporter, error) {
oce, err := newExporter(params.Logger, cfg)
if err != nil {
return nil, err
}

return exporterhelper.NewTraceExporter(
cfg,
oce.pushTraceData,
exporterhelper.WithStart(oce.startTraceExporter),
exporterhelper.WithShutdown(oce.shutdown),
)
}
174 changes: 174 additions & 0 deletions exporter/routingexporter/factory_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
// 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
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package routingexporter

import (
"context"
"errors"
"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/configmodels"
)

func TestExporterGetsCreatedWithValidConfiguration(t *testing.T) {
// prepare
factory := NewFactory()
creationParams := component.ExporterCreateParams{Logger: zap.NewNop()}
cfg := &Config{
ExporterSettings: configmodels.ExporterSettings{
NameVal: "routing",
TypeVal: "routing",
},
DefaultExporters: []string{"otlp"},
FromAttribute: "X-Tenant",
Table: []RoutingTableItem{
{
Value: "acme",
Exporters: []string{"otlp"},
},
},
}

// test
exp, err := factory.CreateTraceExporter(context.Background(), creationParams, cfg)

// verify
assert.Nil(t, err)
assert.NotNil(t, exp)
}

func TestFailOnEmptyConfiguration(t *testing.T) {
// prepare
factory := NewFactory()
creationParams := component.ExporterCreateParams{Logger: zap.NewNop()}
cfg := factory.CreateDefaultConfig()

// test
exp, err := factory.CreateTraceExporter(context.Background(), creationParams, cfg)

// verify
assert.Error(t, err)
assert.Nil(t, exp)
}

func TestExporterFailsToBeCreatedWhenRouteHasNoExporters(t *testing.T) {
// prepare
factory := NewFactory()
creationParams := component.ExporterCreateParams{Logger: zap.NewNop()}
cfg := &Config{
ExporterSettings: configmodels.ExporterSettings{
NameVal: "routing",
TypeVal: "routing",
},
DefaultExporters: []string{"otlp"},
FromAttribute: "X-Tenant",
Table: []RoutingTableItem{
{
Value: "acme",
},
},
}

// test
exp, err := factory.CreateTraceExporter(context.Background(), creationParams, cfg)

// verify
assert.True(t, errors.Is(err, errNoExporters))
assert.Nil(t, exp)
}

func TestExporterFailsToBeCreatedWhenNoRoutesExist(t *testing.T) {
// prepare
factory := NewFactory()
creationParams := component.ExporterCreateParams{Logger: zap.NewNop()}
cfg := &Config{
ExporterSettings: configmodels.ExporterSettings{
NameVal: "routing",
TypeVal: "routing",
},
DefaultExporters: []string{"otlp"},
FromAttribute: "X-Tenant",
Table: []RoutingTableItem{},
}

// test
exp, err := factory.CreateTraceExporter(context.Background(), creationParams, cfg)

// verify
assert.True(t, errors.Is(err, errNoTableItems))
assert.Nil(t, exp)
}

func TestExporterFailsWithNoFromAttribute(t *testing.T) {
// prepare
factory := NewFactory()
creationParams := component.ExporterCreateParams{Logger: zap.NewNop()}
cfg := &Config{
ExporterSettings: configmodels.ExporterSettings{
NameVal: "routing",
TypeVal: "routing",
},
DefaultExporters: []string{"otlp"},
Table: []RoutingTableItem{
{
Value: "acme",
Exporters: []string{"otlp"},
},
},
}

// test
exp, err := factory.CreateTraceExporter(context.Background(), creationParams, cfg)

// verify
assert.True(t, errors.Is(err, errNoMissingFromAttribute))
assert.Nil(t, exp)
}

func TestShutdown(t *testing.T) {
// prepare
factory := NewFactory()
creationParams := component.ExporterCreateParams{Logger: zap.NewNop()}
cfg := &Config{
ExporterSettings: configmodels.ExporterSettings{
NameVal: "routing",
TypeVal: "routing",
},
DefaultExporters: []string{"otlp"},
FromAttribute: "X-Tenant",
Table: []RoutingTableItem{
{
Value: "acme",
Exporters: []string{"otlp"},
},
},
}

exp, err := factory.CreateTraceExporter(context.Background(), creationParams, cfg)
require.Nil(t, err)
require.NotNil(t, exp)

// test
err = exp.Shutdown(context.Background())

// verify
assert.NoError(t, err)

}
Loading

0 comments on commit 4115bf6

Please sign in to comment.