From dccafacf29fee52aabb37aafe3be312c2f8bc4c6 Mon Sep 17 00:00:00 2001 From: Evan Bradley <11745660+evan-bradley@users.noreply.github.com> Date: Tue, 11 Mar 2025 14:31:18 -0400 Subject: [PATCH 1/5] [service] Allow running the Collector without any pipelines --- .chloggen/allow-no-pipelines.yaml | 25 +++++++++++++++++++++++++ otelcol/config.go | 5 +++-- otelcol/config_test.go | 16 ++++++++++++++++ service/pipelines/config.go | 10 ++++++++-- service/pipelines/config_test.go | 12 ++++++++++++ 5 files changed, 64 insertions(+), 4 deletions(-) create mode 100644 .chloggen/allow-no-pipelines.yaml diff --git a/.chloggen/allow-no-pipelines.yaml b/.chloggen/allow-no-pipelines.yaml new file mode 100644 index 00000000000..7f80b6d17dc --- /dev/null +++ b/.chloggen/allow-no-pipelines.yaml @@ -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: enhancement + +# The name of the component, or a single word describing the area of concern, (e.g. otlpreceiver) +component: service + +# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). +note: Add `service.AllowNoPipelines` feature gate to allow starting the Collector without pipelines. + +# One or more tracking issues or pull requests related to the change +issues: [] + +# (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: This can be used to start with only extensions. + +# 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: [] diff --git a/otelcol/config.go b/otelcol/config.go index 2bfd863e2f2..fa2a624f2e9 100644 --- a/otelcol/config.go +++ b/otelcol/config.go @@ -9,6 +9,7 @@ import ( "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/service" + "go.opentelemetry.io/collector/service/pipelines" ) var ( @@ -50,13 +51,13 @@ func (cfg *Config) Validate() error { // Currently, there is no default receiver enabled. // The configuration must specify at least one receiver to be valid. - if len(cfg.Receivers) == 0 { + if !pipelines.AllowNoPipelines.IsEnabled() && len(cfg.Receivers) == 0 { return errMissingReceivers } // Currently, there is no default exporter enabled. // The configuration must specify at least one exporter to be valid. - if len(cfg.Exporters) == 0 { + if !pipelines.AllowNoPipelines.IsEnabled() && len(cfg.Exporters) == 0 { return errMissingExporters } diff --git a/otelcol/config_test.go b/otelcol/config_test.go index 19970060926..ba0db7ab4c3 100644 --- a/otelcol/config_test.go +++ b/otelcol/config_test.go @@ -15,6 +15,7 @@ import ( "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/config/configtelemetry" "go.opentelemetry.io/collector/confmap/xconfmap" + "go.opentelemetry.io/collector/featuregate" "go.opentelemetry.io/collector/pipeline" "go.opentelemetry.io/collector/service" "go.opentelemetry.io/collector/service/pipelines" @@ -252,6 +253,21 @@ func TestConfigValidate(t *testing.T) { } } +func TestNoPipelinesFeatureGate(t *testing.T) { + cfg := generateConfig() + cfg.Receivers = nil + cfg.Exporters = nil + cfg.Service.Pipelines = pipelines.Config{} + + assert.Error(t, xconfmap.Validate(cfg)) + + gate := pipelines.AllowNoPipelines + featuregate.GlobalRegistry().Set(gate.ID(), true) + defer featuregate.GlobalRegistry().Set(gate.ID(), false) + + assert.NoError(t, xconfmap.Validate(cfg)) +} + func generateConfig() *Config { return &Config{ Receivers: map[component.ID]component.Config{ diff --git a/service/pipelines/config.go b/service/pipelines/config.go index 18e2621a86c..2109b09b70c 100644 --- a/service/pipelines/config.go +++ b/service/pipelines/config.go @@ -25,14 +25,20 @@ var ( featuregate.WithRegisterFromVersion("v0.112.0"), featuregate.WithRegisterDescription("Controls whether profiles support can be enabled"), ) + AllowNoPipelines = featuregate.GlobalRegistry().MustRegister( + "service.AllowNoPipelines", + featuregate.StageAlpha, + featuregate.WithRegisterFromVersion("v0.122.0"), + featuregate.WithRegisterDescription("Allow starting the Collector without starting any pipelines."), + ) ) // Config defines the configurable settings for service telemetry. type Config map[pipeline.ID]*PipelineConfig func (cfg Config) Validate() error { - // Must have at least one pipeline. - if len(cfg) == 0 { + // Must have at least one pipeline unless explicitly disabled. + if !AllowNoPipelines.IsEnabled() && len(cfg) == 0 { return errMissingServicePipelines } diff --git a/service/pipelines/config_test.go b/service/pipelines/config_test.go index c24f8c744c9..c97c9132de1 100644 --- a/service/pipelines/config_test.go +++ b/service/pipelines/config_test.go @@ -120,6 +120,18 @@ func TestConfigValidate(t *testing.T) { } } +func TestNoPipelinesFeatureGate(t *testing.T) { + cfg := Config{} + + require.Error(t, xconfmap.Validate(cfg)) + + gate := AllowNoPipelines + featuregate.GlobalRegistry().Set(gate.ID(), true) + defer featuregate.GlobalRegistry().Set(gate.ID(), false) + + require.NoError(t, xconfmap.Validate(cfg)) +} + func generateConfig(t *testing.T) Config { t.Helper() From c4ca61ec3ac40f8c91aa0c64c92a132ad4bb16ba Mon Sep 17 00:00:00 2001 From: Evan Bradley <11745660+evan-bradley@users.noreply.github.com> Date: Tue, 11 Mar 2025 14:39:06 -0400 Subject: [PATCH 2/5] Add issue number --- .chloggen/allow-no-pipelines.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.chloggen/allow-no-pipelines.yaml b/.chloggen/allow-no-pipelines.yaml index 7f80b6d17dc..5f47198d66d 100644 --- a/.chloggen/allow-no-pipelines.yaml +++ b/.chloggen/allow-no-pipelines.yaml @@ -10,7 +10,7 @@ component: service note: Add `service.AllowNoPipelines` feature gate to allow starting the Collector without pipelines. # One or more tracking issues or pull requests related to the change -issues: [] +issues: [12613] # (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. From 953351c6076eb70cf585ee9c440feb6dba690c4f Mon Sep 17 00:00:00 2001 From: Evan Bradley <11745660+evan-bradley@users.noreply.github.com> Date: Tue, 11 Mar 2025 14:50:06 -0400 Subject: [PATCH 3/5] Fix lint --- otelcol/config_test.go | 4 ++-- service/pipelines/config_test.go | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/otelcol/config_test.go b/otelcol/config_test.go index ba0db7ab4c3..41b6b00b527 100644 --- a/otelcol/config_test.go +++ b/otelcol/config_test.go @@ -262,8 +262,8 @@ func TestNoPipelinesFeatureGate(t *testing.T) { assert.Error(t, xconfmap.Validate(cfg)) gate := pipelines.AllowNoPipelines - featuregate.GlobalRegistry().Set(gate.ID(), true) - defer featuregate.GlobalRegistry().Set(gate.ID(), false) + assert.NoError(t, featuregate.GlobalRegistry().Set(gate.ID(), true)) + defer assert.NoError(t, featuregate.GlobalRegistry().Set(gate.ID(), false)) assert.NoError(t, xconfmap.Validate(cfg)) } diff --git a/service/pipelines/config_test.go b/service/pipelines/config_test.go index c97c9132de1..4bb885c446a 100644 --- a/service/pipelines/config_test.go +++ b/service/pipelines/config_test.go @@ -126,8 +126,9 @@ func TestNoPipelinesFeatureGate(t *testing.T) { require.Error(t, xconfmap.Validate(cfg)) gate := AllowNoPipelines - featuregate.GlobalRegistry().Set(gate.ID(), true) - defer featuregate.GlobalRegistry().Set(gate.ID(), false) + err := featuregate.GlobalRegistry().Set(gate.ID(), true) + require.NoError(t, err) + defer require.NoError(t, featuregate.GlobalRegistry().Set(gate.ID(), false)) require.NoError(t, xconfmap.Validate(cfg)) } From 3962f5cd4253f10640d0f75cd3d56500fe4b9154 Mon Sep 17 00:00:00 2001 From: Evan Bradley <11745660+evan-bradley@users.noreply.github.com> Date: Tue, 11 Mar 2025 14:55:09 -0400 Subject: [PATCH 4/5] Fix improper defer call --- otelcol/config_test.go | 4 +++- service/pipelines/config_test.go | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/otelcol/config_test.go b/otelcol/config_test.go index 41b6b00b527..cbee69e330c 100644 --- a/otelcol/config_test.go +++ b/otelcol/config_test.go @@ -263,7 +263,9 @@ func TestNoPipelinesFeatureGate(t *testing.T) { gate := pipelines.AllowNoPipelines assert.NoError(t, featuregate.GlobalRegistry().Set(gate.ID(), true)) - defer assert.NoError(t, featuregate.GlobalRegistry().Set(gate.ID(), false)) + defer func() { + assert.NoError(t, featuregate.GlobalRegistry().Set(gate.ID(), false)) + }() assert.NoError(t, xconfmap.Validate(cfg)) } diff --git a/service/pipelines/config_test.go b/service/pipelines/config_test.go index 4bb885c446a..49813335161 100644 --- a/service/pipelines/config_test.go +++ b/service/pipelines/config_test.go @@ -128,7 +128,9 @@ func TestNoPipelinesFeatureGate(t *testing.T) { gate := AllowNoPipelines err := featuregate.GlobalRegistry().Set(gate.ID(), true) require.NoError(t, err) - defer require.NoError(t, featuregate.GlobalRegistry().Set(gate.ID(), false)) + defer func() { + require.NoError(t, featuregate.GlobalRegistry().Set(gate.ID(), false)) + }() require.NoError(t, xconfmap.Validate(cfg)) } From c255a2c403ef93737a793787c73a65699aa04988 Mon Sep 17 00:00:00 2001 From: Evan Bradley <11745660+evan-bradley@users.noreply.github.com> Date: Tue, 11 Mar 2025 15:01:50 -0400 Subject: [PATCH 5/5] Fix lint --- otelcol/config_test.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/otelcol/config_test.go b/otelcol/config_test.go index cbee69e330c..654125750a8 100644 --- a/otelcol/config_test.go +++ b/otelcol/config_test.go @@ -8,7 +8,7 @@ import ( "fmt" "testing" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" config "go.opentelemetry.io/contrib/otelconf/v0.3.0" "go.uber.org/zap/zapcore" @@ -245,9 +245,9 @@ func TestConfigValidate(t *testing.T) { cfg := tt.cfgFn() err := xconfmap.Validate(cfg) if tt.expected != nil { - assert.EqualError(t, err, tt.expected.Error()) + require.EqualError(t, err, tt.expected.Error()) } else { - assert.NoError(t, err) + require.NoError(t, err) } }) } @@ -259,15 +259,15 @@ func TestNoPipelinesFeatureGate(t *testing.T) { cfg.Exporters = nil cfg.Service.Pipelines = pipelines.Config{} - assert.Error(t, xconfmap.Validate(cfg)) + require.Error(t, xconfmap.Validate(cfg)) gate := pipelines.AllowNoPipelines - assert.NoError(t, featuregate.GlobalRegistry().Set(gate.ID(), true)) + require.NoError(t, featuregate.GlobalRegistry().Set(gate.ID(), true)) defer func() { - assert.NoError(t, featuregate.GlobalRegistry().Set(gate.ID(), false)) + require.NoError(t, featuregate.GlobalRegistry().Set(gate.ID(), false)) }() - assert.NoError(t, xconfmap.Validate(cfg)) + require.NoError(t, xconfmap.Validate(cfg)) } func generateConfig() *Config {