Skip to content

Commit

Permalink
Add Instana exporter overall structure (#13452)
Browse files Browse the repository at this point in the history
* Add Instana exporter overall structure

Signed-off-by: Martin Hickey <[email protected]>
Co-authored-by: Martin Hickey <[email protected]>
Co-authored-by: Cedric Ziel <[email protected]>
Co-authored-by: Willian Carvalho <[email protected]>

* Update after reviews

Review comments:
- #13452 (review)

Signed-off-by: Martin Hickey <[email protected]>

* Update after review

Review comments:
- #13452 (comment)

Signed-off-by: Martin Hickey <[email protected]>

* Integrate exporter into the components

Signed-off-by: Martin Hickey <[email protected]>

* Update changelog

Signed-off-by: Martin Hickey <[email protected]>

* Add porto changes

Signed-off-by: Martin Hickey <[email protected]>

* unit tests

Signed-off-by: Martin Hickey <[email protected]>

* Update after review

Review comments:
- #13452 (comment)
- #13452 (comment)

Signed-off-by: Martin Hickey <[email protected]>

Signed-off-by: Martin Hickey <[email protected]>
Co-authored-by: Cedric Ziel <[email protected]>
Co-authored-by: Willian Carvalho <[email protected]>
  • Loading branch information
3 people authored Aug 24, 2022
1 parent 383db3a commit 3d3f291
Show file tree
Hide file tree
Showing 12 changed files with 767 additions and 0 deletions.
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ exporter/googlemanagedprometheusexporter/ @open-telemetry/collector-c
exporter/googlecloudpubsubexporter/ @open-telemetry/collector-contrib-approvers @alexvanboxel
exporter/humioexporter/ @open-telemetry/collector-contrib-approvers @xitric
exporter/influxdbexporter/ @open-telemetry/collector-contrib-approvers @jacobmarble
exporter/instanaexporter/ @open-telemetry/collector-contrib-approvers @jpkrohling @hickeyma
exporter/jaegerexporter/ @open-telemetry/collector-contrib-approvers @jpkrohling
exporter/jaegerthrifthttpexporter/ @open-telemetry/collector-contrib-approvers @jpkrohling @pavolloffay
exporter/kafkaexporter/ @open-telemetry/collector-contrib-approvers @pavolloffay @MovieStoreGuy
Expand Down
1 change: 1 addition & 0 deletions exporter/instanaexporter/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
include ../../Makefile.Common
73 changes: 73 additions & 0 deletions exporter/instanaexporter/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Instana Exporter

| Status | |
| ------------------------ |------------------|
| Stability | [in-development] |
| Supported pipeline types | traces |
| Distributions | [contrib] |

The Instana Exporter converts OpenTelemetry trace data and then sends it to the [Instana Backend](https://www.ibm.com/docs/en/instana-observability/current?topic=setting-up-managing-instana).

## Exporter Configuration

The following exporter configuration parameters are supported.


| Parameter | Description |
|----------------|-------------|
| endpoint | The Instana backend endpoint that the Exporter connects to. It depends on your region and it starts with ``https://serverless-``. It corresponds to the Instana environment variable ``INSTANA_ENDPOINT_URL`` |
| agent_key | Your Instana Agent key. The same agent key can be used for host agents and serverless monitoring. It corresponds to the Instana environment variable ``INSTANA_AGENT_KEY`` |

> These parameters match the Instana Serverless Monitoring environment variables and can be found [here](https://www.ibm.com/docs/en/instana-observability/current?topic=references-environment-variables#serverless-monitoring).
### Sample Configuration

The code snippet below shows how your configuration file should look like:

```yaml
[...]

exporters:
instana:
endpoint: ${INSTANA_ENDPOINT_URL}
agent_key: ${INSTANA_AGENT_KEY}

[...]

service:
pipelines:
traces:
exporters: [instana]

[...]
```

### Full Example

```yaml
receivers:
otlp:
protocols:
grpc:
http:

processors:
batch:
exporters:
logging:
loglevel: debug
instana:
loglevel: debug
endpoint: ${INSTANA_ENDPOINT_URL}
agent_key: ${INSTANA_AGENT_KEY}

service:
pipelines:
traces:
receivers: [otlp]
processors: [batch]
exporters: [instana]
```
[in-development]:https://github.com/open-telemetry/opentelemetry-collector#in-development
[contrib]:https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-contrib
54 changes: 54 additions & 0 deletions exporter/instanaexporter/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright 2022, 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 instanaexporter // import "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/instanaexporter"

import (
"errors"
"strings"

"go.opentelemetry.io/collector/config"
"go.opentelemetry.io/collector/config/confighttp"
)

// Config defines configuration for the Instana exporter
type Config struct {
config.ExporterSettings `mapstructure:",squash"` // squash ensures fields are correctly decoded in embedded struct

Endpoint string `mapstructure:"endpoint"`

AgentKey string `mapstructure:"agent_key"`

confighttp.HTTPClientSettings `mapstructure:",squash"`
}

var _ config.Exporter = (*Config)(nil)

// Validate checks if the exporter configuration is valid
func (cfg *Config) Validate() error {

if cfg.Endpoint == "" {
return errors.New("no Instana endpoint set")
}

if cfg.AgentKey == "" {
return errors.New("no Instana agent key set")
}

if !(strings.HasPrefix(cfg.Endpoint, "http://") || strings.HasPrefix(cfg.Endpoint, "https://")) {
return errors.New("endpoint must start with http:// or https://")
}

return nil
}
51 changes: 51 additions & 0 deletions exporter/instanaexporter/config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright 2022, 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 instanaexporter

import (
"testing"

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

func TestConfig_Validate(t *testing.T) {
t.Run("Empty configuration", func(t *testing.T) {
c := &Config{}
err := c.Validate()
assert.Error(t, err)
})

t.Run("Valid configuration", func(t *testing.T) {
c := &Config{Endpoint: "http://example.com/", AgentKey: "key1"}
err := c.Validate()
assert.NoError(t, err)

assert.Equal(t, "http://example.com/", c.Endpoint, "no Instana endpoint set")
})

t.Run("Invalid Endpoint", func(t *testing.T) {
c := &Config{Endpoint: "example.com", AgentKey: "key1"}
err := c.Validate()
assert.Error(t, err)

assert.Equal(t, "example.com", c.Endpoint, "endpoint must start with http:// or https://")
})

t.Run("No Agent key", func(t *testing.T) {
c := &Config{Endpoint: "http://example.com/"}
err := c.Validate()
assert.Error(t, err)
})
}
90 changes: 90 additions & 0 deletions exporter/instanaexporter/factory.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// Copyright 2022, 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 instanaexporter // import "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/instanaexporter"

import (
"context"
"time"

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

const (
// The value of "type" key in configuration.
typeStr = "instana"
// The stability level of the exporter.
stability = component.StabilityLevelInDevelopment
)

// NewFactory creates an Instana exporter factory
func NewFactory() component.ExporterFactory {
return component.NewExporterFactory(
typeStr,
createDefaultConfig,
component.WithTracesExporter(createTracesExporter, stability),
)
}

// createDefaultConfig creates the default exporter configuration
func createDefaultConfig() config.Exporter {
return &Config{
ExporterSettings: config.NewExporterSettings(config.NewComponentID(typeStr)),
HTTPClientSettings: confighttp.HTTPClientSettings{
Endpoint: "",
Timeout: 30 * time.Second,
Headers: map[string]string{},
WriteBufferSize: 512 * 1024,
},
}
}

// createTracesExporter creates a trace exporter based on this configuration
func createTracesExporter(ctx context.Context, set component.ExporterCreateSettings, config config.Exporter) (component.TracesExporter, error) {
// TODO: Lines commented out until implementation is available
// cfg := config.(*Config)

ctx, cancel := context.WithCancel(ctx)

// TODO: Lines commented out until implementation is available
var pushConvertedTraces consumer.ConsumeTracesFunc
/*instanaExporter, err := newInstanaExporter(cfg, set)
if err != nil {
cancel()
return nil, err
}*/

//TODO: Lines commented out until implementation is available
return exporterhelper.NewTracesExporterWithContext(
ctx,
set,
config,
// instanaExporter.pushConvertedTraces,
pushConvertedTraces,
exporterhelper.WithCapabilities(consumer.Capabilities{MutatesData: false}),
// exporterhelper.WithStart(instanaExporter.start),
// Disable Timeout/RetryOnFailure and SendingQueue
exporterhelper.WithTimeout(exporterhelper.TimeoutSettings{Timeout: 0}),
exporterhelper.WithRetry(exporterhelper.RetrySettings{Enabled: false}),
exporterhelper.WithQueue(exporterhelper.QueueSettings{Enabled: false}),
exporterhelper.WithShutdown(func(context.Context) error {
cancel()
return nil
}),
)
}
90 changes: 90 additions & 0 deletions exporter/instanaexporter/factory_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// Copyright 2022, 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 instanaexporter

import (
"path/filepath"
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/config"
"go.opentelemetry.io/collector/config/confighttp"
"go.opentelemetry.io/collector/config/configtest"
"go.opentelemetry.io/collector/service/servicetest"
)

// Test that the factory creates the default configuration
func TestCreateDefaultConfig(t *testing.T) {
factory := NewFactory()
cfg := factory.CreateDefaultConfig()

assert.Equal(t, &Config{
ExporterSettings: config.NewExporterSettings(config.NewComponentID(typeStr)),
HTTPClientSettings: confighttp.HTTPClientSettings{
Endpoint: "",
Timeout: 30 * time.Second,
Headers: map[string]string{},
WriteBufferSize: 512 * 1024,
},
}, cfg, "failed to create default config")

assert.NoError(t, configtest.CheckConfigStruct(cfg))
}

// TestLoadConfig tests that the configuration is loaded correctly
func TestLoadConfig(t *testing.T) {
factories, err := componenttest.NopFactories()
assert.NoError(t, err)

factory := NewFactory()
factories.Exporters[typeStr] = factory
cfg, err := servicetest.LoadConfig(filepath.Join("testdata", "config.yml"), factories)

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

t.Run("valid config", func(t *testing.T) {
validConfig := cfg.Exporters[config.NewComponentIDWithName(typeStr, "valid")].(*Config)
err = validConfig.Validate()

require.NoError(t, err)
assert.Equal(t, &Config{
ExporterSettings: config.NewExporterSettings(config.NewComponentIDWithName(typeStr, "valid")),
HTTPClientSettings: confighttp.HTTPClientSettings{
Endpoint: "http://example.com/api/",
Timeout: 30 * time.Second,
Headers: map[string]string{},
WriteBufferSize: 512 * 1024,
},
Endpoint: "http://example.com/api/",
AgentKey: "key1",
}, validConfig)
})

t.Run("bad endpoint", func(t *testing.T) {
badEndpointConfig := cfg.Exporters[config.NewComponentIDWithName(typeStr, "bad_endpoint")].(*Config)
err = badEndpointConfig.Validate()
require.Error(t, err)
})

t.Run("missing agent key", func(t *testing.T) {
missingAgentConfig := cfg.Exporters[config.NewComponentIDWithName(typeStr, "missing_agent_key")].(*Config)
err = missingAgentConfig.Validate()
require.Error(t, err)
})
}
Loading

0 comments on commit 3d3f291

Please sign in to comment.