Skip to content

Commit

Permalink
Move extension builder into internal service (#10785)
Browse files Browse the repository at this point in the history
<!--Ex. Fixing a bug - Describe the bug and how this fixes the issue.
Ex. Adding a feature - Explain what this achieves.-->
#### Description

This moves the connector builder out of the `connector` package, and
into `service/internal/builders`.
There's no real reason for this struct to be public (folks shouldn't
call it), and making it private will allow us to add profiling support
to it.

<!-- Issue number if applicable -->
#### Link to tracking issue

#10375 (review)

While this is not technically required for the profiles work (there is
no notion of signals in extensions), this PR is here to keep things
consistent.
  • Loading branch information
dmathieu committed Aug 22, 2024
1 parent a0331ed commit 549ee72
Show file tree
Hide file tree
Showing 12 changed files with 248 additions and 33 deletions.
25 changes: 25 additions & 0 deletions .chloggen/private-extension-builder.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Use this changelog template to create an entry for release notes.

# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
change_type: deprecation

# The name of the component, or a single word describing the area of concern, (e.g. otlpreceiver)
component: extension

# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
note: Deprecate extension.Builder, and move it into an internal package of the service module

# One or more tracking issues or pull requests related to the change
issues: [10785]

# (Optional) One or more lines of additional information to render under the primary note.
# These lines will be padded with 2 spaces and then inserted directly into the document.
# Use pipe (|) for multiline entries.
subtext:

# Optional: The change log or logs in which this entry should be included.
# e.g. '[user]' or '[user, api]'
# Include 'user' if the change is relevant to end users.
# Include 'api' if there is a change to a library API.
# Default: '[user]'
change_logs: [api]
6 changes: 6 additions & 0 deletions extension/extension.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,12 +139,18 @@ func MakeFactoryMap(factories ...Factory) (map[component.Type]Factory, error) {
}

// Builder extension is a helper struct that given a set of Configs and Factories helps with creating extensions.
//
// Deprecated: [v0.108.0] this builder is being internalized within the service module,
// and will be removed soon.
type Builder struct {
cfgs map[component.ID]component.Config
factories map[component.Type]Factory
}

// NewBuilder creates a new extension.Builder to help with creating components form a set of configs and factories.
//
// Deprecated: [v0.108.0] this builder is being internalized within the service module,
// and will be removed soon.
func NewBuilder(cfgs map[component.ID]component.Config, factories map[component.Type]Factory) *Builder {
return &Builder{cfgs: cfgs, factories: factories}
}
Expand Down
3 changes: 3 additions & 0 deletions extension/extensiontest/nop_extension.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ type nopExtension struct {
}

// NewNopBuilder returns a extension.Builder that constructs nop extension.
//
// Deprecated: [v0.108.0] this builder is being internalized within the service module,
// and will be removed soon.
func NewNopBuilder() *extension.Builder {
nopFactory := NewNopFactory()
return extension.NewBuilder(
Expand Down
13 changes: 6 additions & 7 deletions internal/e2e/status_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,12 @@ func Test_ComponentStatusReporting_SharedInstance(t *testing.T) {
ConnectorsFactories: map[component.Type]connector.Factory{
nopType: connectorFactory,
},
Extensions: extension.NewBuilder(
map[component.ID]component.Config{
component.NewID(component.MustNewType("watcher")): &extensionConfig{eventsReceived},
},
map[component.Type]extension.Factory{
component.MustNewType("watcher"): newExtensionFactory(),
}),
ExtensionsConfigs: map[component.ID]component.Config{
component.NewID(component.MustNewType("watcher")): &extensionConfig{eventsReceived},
},
ExtensionsFactories: map[component.Type]extension.Factory{
component.MustNewType("watcher"): newExtensionFactory(),
},
}
set.BuildInfo = component.BuildInfo{Version: "test version", Command: "otelcoltest"}

Expand Down
9 changes: 6 additions & 3 deletions otelcol/collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,15 +183,18 @@ func (col *Collector) setupConfigurationComponents(ctx context.Context) error {
}

col.service, err = service.New(ctx, service.Settings{
BuildInfo: col.set.BuildInfo,
CollectorConf: conf,
BuildInfo: col.set.BuildInfo,
CollectorConf: conf,

ReceiversConfigs: cfg.Receivers,
ReceiversFactories: factories.Receivers,
Processors: processor.NewBuilder(cfg.Processors, factories.Processors),
Exporters: exporter.NewBuilder(cfg.Exporters, factories.Exporters),
ConnectorsConfigs: cfg.Connectors,
ConnectorsFactories: factories.Connectors,
Extensions: extension.NewBuilder(cfg.Extensions, factories.Extensions),
ExtensionsConfigs: cfg.Extensions,
ExtensionsFactories: factories.Extensions,

ModuleInfo: extension.ModuleInfo{
Receiver: factories.ReceiverModules,
Processor: factories.ProcessorModules,
Expand Down
9 changes: 5 additions & 4 deletions service/extensions/extensions.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"go.opentelemetry.io/collector/component/componentstatus"
"go.opentelemetry.io/collector/confmap"
"go.opentelemetry.io/collector/extension"
"go.opentelemetry.io/collector/service/internal/builders"
"go.opentelemetry.io/collector/service/internal/components"
"go.opentelemetry.io/collector/service/internal/status"
"go.opentelemetry.io/collector/service/internal/zpages"
Expand Down Expand Up @@ -168,12 +169,12 @@ func (bes *Extensions) HandleZPages(w http.ResponseWriter, r *http.Request) {

// Settings holds configuration for building Extensions.
type Settings struct {
Telemetry component.TelemetrySettings
BuildInfo component.BuildInfo
Telemetry component.TelemetrySettings
BuildInfo component.BuildInfo
ModuleInfo extension.ModuleInfo

// Extensions builder for extensions.
Extensions *extension.Builder
ModuleInfo extension.ModuleInfo
Extensions builders.Extension
}

type Option func(*Extensions)
Expand Down
9 changes: 5 additions & 4 deletions service/extensions/extensions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"go.opentelemetry.io/collector/confmap"
"go.opentelemetry.io/collector/extension"
"go.opentelemetry.io/collector/extension/extensiontest"
"go.opentelemetry.io/collector/service/internal/builders"
"go.opentelemetry.io/collector/service/internal/status"
)

Expand Down Expand Up @@ -86,7 +87,7 @@ func TestBuildExtensions(t *testing.T) {
_, err := New(context.Background(), Settings{
Telemetry: componenttest.NewNopTelemetrySettings(),
BuildInfo: component.NewDefaultBuildInfo(),
Extensions: extension.NewBuilder(tt.extensionsConfigs, tt.factories),
Extensions: builders.NewExtension(tt.extensionsConfigs, tt.factories),
}, tt.config)
require.Error(t, err)
assert.EqualError(t, err, tt.wantErrMsg)
Expand Down Expand Up @@ -178,7 +179,7 @@ func (tc testOrderCase) testOrdering(t *testing.T) {
exts, err := New(context.Background(), Settings{
Telemetry: componenttest.NewNopTelemetrySettings(),
BuildInfo: component.NewDefaultBuildInfo(),
Extensions: extension.NewBuilder(
Extensions: builders.NewExtension(
extCfgs,
map[component.Type]extension.Factory{
recordingExtensionFactory.Type(): recordingExtensionFactory,
Expand Down Expand Up @@ -280,7 +281,7 @@ func TestNotifyConfig(t *testing.T) {
extensions, err := New(context.Background(), Settings{
Telemetry: componenttest.NewNopTelemetrySettings(),
BuildInfo: component.NewDefaultBuildInfo(),
Extensions: extension.NewBuilder(tt.extensionsConfigs, tt.factories),
Extensions: builders.NewExtension(tt.extensionsConfigs, tt.factories),
}, tt.serviceExtensions)
assert.NoError(t, err)
errs := extensions.NotifyConfig(context.Background(), confmap.NewFromStringMap(map[string]interface{}{}))
Expand Down Expand Up @@ -427,7 +428,7 @@ func TestStatusReportedOnStartupShutdown(t *testing.T) {
Settings{
Telemetry: componenttest.NewNopTelemetrySettings(),
BuildInfo: component.NewDefaultBuildInfo(),
Extensions: extension.NewBuilder(extensionsConfigs, factories),
Extensions: builders.NewExtension(extensionsConfigs, factories),
},
[]component.ID{compID},
WithReporter(rep),
Expand Down
69 changes: 69 additions & 0 deletions service/internal/builders/extension.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package builders // import "go.opentelemetry.io/collector/service/internal/builders"

import (
"context"
"fmt"

"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/extension"
"go.opentelemetry.io/collector/extension/extensiontest"
)

// Extension is an interface that allows using implementations of the builder
// from different packages.
type Extension interface {
Create(context.Context, extension.Settings) (extension.Extension, error)
Factory(component.Type) component.Factory
}

// ExtensionBuilder is a helper struct that given a set of Configs and Factories helps with creating extensions.
type ExtensionBuilder struct {
cfgs map[component.ID]component.Config
factories map[component.Type]extension.Factory
}

// NewExtension creates a new ExtensionBuilder to help with creating
// components form a set of configs and factories.
func NewExtension(cfgs map[component.ID]component.Config, factories map[component.Type]extension.Factory) *ExtensionBuilder {
return &ExtensionBuilder{cfgs: cfgs, factories: factories}
}

// Create creates an extension based on the settings and configs available.
func (b *ExtensionBuilder) Create(ctx context.Context, set extension.Settings) (extension.Extension, error) {
cfg, existsCfg := b.cfgs[set.ID]
if !existsCfg {
return nil, fmt.Errorf("extension %q is not configured", set.ID)
}

f, existsFactory := b.factories[set.ID.Type()]
if !existsFactory {
return nil, fmt.Errorf("extension factory not available for: %q", set.ID)
}

sl := f.ExtensionStability()
if sl >= component.StabilityLevelAlpha {
set.Logger.Debug(sl.LogMessage())
} else {
set.Logger.Info(sl.LogMessage())
}
return f.CreateExtension(ctx, set, cfg)
}

func (b *ExtensionBuilder) Factory(componentType component.Type) component.Factory {
return b.factories[componentType]
}

// NewNopProcessorConfigsAndFactories returns a configuration and factories that allows building a new nop processor.
func NewNopExtensionConfigsAndFactories() (map[component.ID]component.Config, map[component.Type]extension.Factory) {
nopFactory := extensiontest.NewNopFactory()
configs := map[component.ID]component.Config{
component.NewID(nopType): nopFactory.CreateDefaultConfig(),
}
factories := map[component.Type]extension.Factory{
nopType: nopFactory,
}
return configs, factories
}
97 changes: 97 additions & 0 deletions service/internal/builders/extension_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package builders

import (
"context"
"testing"

"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"
"go.opentelemetry.io/collector/extension/extensiontest"
)

func TestExtensionBuilder(t *testing.T) {
var testType = component.MustNewType("test")
defaultCfg := struct{}{}
testID := component.NewID(testType)
unknownID := component.MustNewID("unknown")

factories, err := extension.MakeFactoryMap([]extension.Factory{
extension.NewFactory(
testType,
func() component.Config { return &defaultCfg },
func(_ context.Context, settings extension.Settings, _ component.Config) (extension.Extension, error) {
return nopExtension{Settings: settings}, nil
},
component.StabilityLevelDevelopment),
}...)
require.NoError(t, err)

cfgs := map[component.ID]component.Config{testID: defaultCfg, unknownID: defaultCfg}
b := NewExtension(cfgs, factories)

e, err := b.Create(context.Background(), createExtensionSettings(testID))
assert.NoError(t, err)
assert.NotNil(t, e)

// Check that the extension has access to the resource attributes.
nop, ok := e.(nopExtension)
assert.True(t, ok)
assert.Equal(t, nop.Settings.Resource.Attributes().Len(), 0)

missingType, err := b.Create(context.Background(), createExtensionSettings(unknownID))
assert.EqualError(t, err, "extension factory not available for: \"unknown\"")
assert.Nil(t, missingType)

missingCfg, err := b.Create(context.Background(), createExtensionSettings(component.NewIDWithName(testType, "foo")))
assert.EqualError(t, err, "extension \"test/foo\" is not configured")
assert.Nil(t, missingCfg)
}

func TestExtensionBuilderFactory(t *testing.T) {
factories, err := extension.MakeFactoryMap([]extension.Factory{extension.NewFactory(component.MustNewType("foo"), nil, nil, component.StabilityLevelDevelopment)}...)
require.NoError(t, err)

cfgs := map[component.ID]component.Config{component.MustNewID("foo"): struct{}{}}
b := NewExtension(cfgs, factories)

assert.NotNil(t, b.Factory(component.MustNewID("foo").Type()))
assert.Nil(t, b.Factory(component.MustNewID("bar").Type()))
}

func TestNewNopExtensionConfigsAndFactories(t *testing.T) {
configs, factories := NewNopExtensionConfigsAndFactories()
builder := NewExtension(configs, factories)
require.NotNil(t, builder)

factory := extensiontest.NewNopFactory()
cfg := factory.CreateDefaultConfig()
set := extensiontest.NewNopSettings()
set.ID = component.NewID(nopType)

ext, err := factory.CreateExtension(context.Background(), set, cfg)
require.NoError(t, err)
bExt, err := builder.Create(context.Background(), set)
require.NoError(t, err)
assert.IsType(t, ext, bExt)
}

type nopExtension struct {
component.StartFunc
component.ShutdownFunc
extension.Settings
}

func createExtensionSettings(id component.ID) extension.Settings {
return extension.Settings{
ID: id,
TelemetrySettings: componenttest.NewNopTelemetrySettings(),
BuildInfo: component.NewDefaultBuildInfo(),
}
}
2 changes: 1 addition & 1 deletion service/internal/graph/host.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ type Host struct {
Processors *processor.Builder
Exporters *exporter.Builder
Connectors builders.Connector
Extensions *extension.Builder
Extensions builders.Extension

ModuleInfo extension.ModuleInfo
BuildInfo component.BuildInfo
Expand Down
13 changes: 11 additions & 2 deletions service/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,11 @@ type Settings struct {
ConnectorsFactories map[component.Type]connector.Factory

// Extensions builder for extensions.
Extensions *extension.Builder
Extensions builders.Extension

// Extensions configuration to its builder.
ExtensionsConfigs map[component.ID]component.Config
ExtensionsFactories map[component.Type]extension.Factory

// ModuleInfo describes the go module for each component.
ModuleInfo extension.ModuleInfo
Expand Down Expand Up @@ -107,14 +111,19 @@ func New(ctx context.Context, set Settings, cfg Config) (*Service, error) {
connectors = builders.NewConnector(set.ConnectorsConfigs, set.ConnectorsFactories)
}

extensions := set.Extensions
if extensions == nil {
extensions = builders.NewExtension(set.ExtensionsConfigs, set.ExtensionsFactories)
}

srv := &Service{
buildInfo: set.BuildInfo,
host: &graph.Host{
Receivers: receivers,
Processors: set.Processors,
Exporters: set.Exporters,
Connectors: connectors,
Extensions: set.Extensions,
Extensions: extensions,
ModuleInfo: set.ModuleInfo,
BuildInfo: set.BuildInfo,
AsyncErrorChannel: set.AsyncErrorChannel,
Expand Down
Loading

0 comments on commit 549ee72

Please sign in to comment.