Skip to content

Commit bea9ed8

Browse files
committed
feat(migrations): Add migration for inputs.kafka_consumer connection_strategy option
The connection_strategy option was deprecated in v1.33.0 for removal in v1.40.0, replaced by startup_error_behavior. Remove it and add a migration mapping "defer" to startup_error_behavior = "retry", dropping the "startup" default and erroring on contradicting values.
1 parent 0ab75b1 commit bea9ed8

9 files changed

Lines changed: 274 additions & 91 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
//go:build !custom || (migrations && (inputs || inputs.kafka_consumer))
2+
3+
package all
4+
5+
import _ "github.com/influxdata/telegraf/migrations/inputs_kafka_consumer" // register migration
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package inputs_kafka_consumer
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"strings"
7+
8+
"github.com/influxdata/toml"
9+
"github.com/influxdata/toml/ast"
10+
11+
"github.com/influxdata/telegraf/migrations"
12+
)
13+
14+
// Migration function to migrate the deprecated 'connection_strategy' option to
15+
// its replacement 'startup_error_behavior'. The "defer" strategy, which allowed
16+
// the plugin to start even if the broker was unavailable, maps to the "retry"
17+
// behavior. The "startup" strategy (and the empty default) corresponds to the
18+
// default "error" behavior, so the option is simply removed in that case.
19+
func migrate(tbl *ast.Table) ([]byte, string, error) {
20+
// Decode the old data structure
21+
var plugin map[string]interface{}
22+
if err := toml.UnmarshalTable(tbl, &plugin); err != nil {
23+
return nil, "", err
24+
}
25+
26+
// Check for the deprecated option and migrate it
27+
var applied bool
28+
if rawOldValue, found := plugin["connection_strategy"]; found {
29+
applied = true
30+
31+
// Convert to the actual type
32+
oldValue, ok := rawOldValue.(string)
33+
if !ok {
34+
return nil, "", fmt.Errorf("unexpected type %T for 'connection_strategy'", rawOldValue)
35+
}
36+
37+
switch strings.ToLower(oldValue) {
38+
case "", "startup":
39+
// Default behavior corresponding to 'startup_error_behavior = "error"'
40+
case "defer":
41+
// Check if the new option already exists and if it has a
42+
// contradicting value. If the new option is not present, migrate
43+
// the old value to the equivalent behavior.
44+
if rawNewValue, found := plugin["startup_error_behavior"]; found {
45+
if newValue, ok := rawNewValue.(string); !ok {
46+
return nil, "", fmt.Errorf("unexpected type %T for 'startup_error_behavior'", rawNewValue)
47+
} else if newValue != "retry" {
48+
return nil, "", errors.New("contradicting setting for 'startup_error_behavior' and 'connection_strategy'")
49+
}
50+
} else {
51+
plugin["startup_error_behavior"] = "retry"
52+
}
53+
default:
54+
return nil, "", fmt.Errorf("invalid connection strategy %q", oldValue)
55+
}
56+
57+
// Remove the deprecated setting
58+
delete(plugin, "connection_strategy")
59+
}
60+
61+
// No options migrated so we can exit early
62+
if !applied {
63+
return nil, "", migrations.ErrNotApplicable
64+
}
65+
66+
// Create the corresponding plugin configuration
67+
cfg := migrations.CreateTOMLStruct("inputs", "kafka_consumer")
68+
cfg.Add("inputs", "kafka_consumer", plugin)
69+
70+
output, err := toml.Marshal(cfg)
71+
return output, "", err
72+
}
73+
74+
// Register the migration function for the plugin type
75+
func init() {
76+
migrations.AddPluginOptionMigration("inputs.kafka_consumer", migrate)
77+
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package inputs_kafka_consumer_test
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
"testing"
7+
8+
"github.com/stretchr/testify/require"
9+
10+
"github.com/influxdata/telegraf/config"
11+
_ "github.com/influxdata/telegraf/migrations/inputs_kafka_consumer" // register migration
12+
"github.com/influxdata/telegraf/plugins/inputs/kafka_consumer" // register plugin
13+
_ "github.com/influxdata/telegraf/plugins/parsers/influx" // register parser
14+
)
15+
16+
func TestNoMigration(t *testing.T) {
17+
plugin := &kafka_consumer.KafkaConsumer{}
18+
defaultCfg := plugin.SampleConfig()
19+
20+
// Migrate and check that nothing changed
21+
output, n, err := config.ApplyMigrations([]byte(defaultCfg))
22+
require.NoError(t, err)
23+
require.NotEmpty(t, output)
24+
require.Zero(t, n)
25+
require.Equal(t, defaultCfg, string(output))
26+
}
27+
28+
func TestStartupErrorBehaviorConflict(t *testing.T) {
29+
cfg := []byte(`
30+
[[inputs.kafka_consumer]]
31+
brokers = ["localhost:9092"]
32+
topics = ["telegraf"]
33+
connection_strategy = "defer"
34+
startup_error_behavior = "ignore"
35+
`)
36+
// Migrate and check that the contradicting settings are caught
37+
output, n, err := config.ApplyMigrations(cfg)
38+
require.ErrorContains(t, err, "contradicting setting for 'startup_error_behavior' and 'connection_strategy'")
39+
require.Empty(t, output)
40+
require.Zero(t, n)
41+
}
42+
43+
func TestInvalidConnectionStrategy(t *testing.T) {
44+
cfg := []byte(`
45+
[[inputs.kafka_consumer]]
46+
brokers = ["localhost:9092"]
47+
topics = ["telegraf"]
48+
connection_strategy = "invalid"
49+
`)
50+
// Migrate and check that the invalid value is caught
51+
output, n, err := config.ApplyMigrations(cfg)
52+
require.ErrorContains(t, err, `invalid connection strategy "invalid"`)
53+
require.Empty(t, output)
54+
require.Zero(t, n)
55+
}
56+
57+
func TestCases(t *testing.T) {
58+
// Get all directories in testcases
59+
folders, err := os.ReadDir("testcases")
60+
require.NoError(t, err)
61+
62+
for _, f := range folders {
63+
// Only handle folders
64+
if !f.IsDir() {
65+
continue
66+
}
67+
68+
t.Run(f.Name(), func(t *testing.T) {
69+
testcasePath := filepath.Join("testcases", f.Name())
70+
inputFile := filepath.Join(testcasePath, "telegraf.conf")
71+
expectedFile := filepath.Join(testcasePath, "expected.conf")
72+
73+
// Read the expected output
74+
expected := config.NewConfig()
75+
require.NoError(t, expected.LoadConfig(expectedFile))
76+
require.NotEmpty(t, expected.Inputs)
77+
78+
// Read the input data
79+
input, remote, err := config.LoadConfigFile(inputFile)
80+
require.NoError(t, err)
81+
require.False(t, remote)
82+
require.NotEmpty(t, input)
83+
84+
// Migrate
85+
output, n, err := config.ApplyMigrations(input)
86+
require.NoError(t, err)
87+
require.NotEmpty(t, output)
88+
require.GreaterOrEqual(t, n, uint64(1))
89+
actual := config.NewConfig()
90+
require.NoError(t, actual.LoadConfigData(output, config.EmptySourcePath))
91+
92+
// Test the output
93+
require.Len(t, actual.Inputs, len(expected.Inputs))
94+
actualIDs := make([]string, 0, len(expected.Inputs))
95+
expectedIDs := make([]string, 0, len(expected.Inputs))
96+
for i := range actual.Inputs {
97+
actualIDs = append(actualIDs, actual.Inputs[i].ID())
98+
expectedIDs = append(expectedIDs, expected.Inputs[i].ID())
99+
}
100+
require.ElementsMatch(t, expectedIDs, actualIDs, string(output))
101+
})
102+
}
103+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Read metrics from Kafka topics
2+
[[inputs.kafka_consumer]]
3+
## Kafka brokers.
4+
brokers = ["localhost:9092"]
5+
6+
## Topics to consume.
7+
topics = ["telegraf"]
8+
9+
## Allow the plugin to start before the broker is available
10+
startup_error_behavior = "retry"
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Read metrics from Kafka topics
2+
[[inputs.kafka_consumer]]
3+
## Kafka brokers.
4+
brokers = ["localhost:9092"]
5+
6+
## Topics to consume.
7+
topics = ["telegraf"]
8+
9+
## Allow the plugin to start before the broker is available
10+
connection_strategy = "defer"
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Read metrics from Kafka topics
2+
[[inputs.kafka_consumer]]
3+
## Kafka brokers.
4+
brokers = ["localhost:9092"]
5+
6+
## Topics to consume.
7+
topics = ["telegraf"]
8+
9+
## Connect to the broker at startup
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Read metrics from Kafka topics
2+
[[inputs.kafka_consumer]]
3+
## Kafka brokers.
4+
brokers = ["localhost:9092"]
5+
6+
## Topics to consume.
7+
topics = ["telegraf"]
8+
9+
## Connect to the broker at startup
10+
connection_strategy = "startup"

plugins/inputs/kafka_consumer/kafka_consumer.go

Lines changed: 6 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,6 @@ type KafkaConsumer struct {
5151
ConsumerFetchDefault config.Size `toml:"consumer_fetch_default"`
5252
ConsumerFetchMin config.Size `toml:"consumer_fetch_min"`
5353
ConsumerFetchMaxWait config.Duration `toml:"consumer_fetch_max_wait"`
54-
ConnectionStrategy string `toml:"connection_strategy" deprecated:"1.33.0;1.40.0;use 'startup_error_behavior' instead"`
5554
ResolveCanonicalBootstrapServersOnly bool `toml:"resolve_canonical_bootstrap_servers_only"`
5655
Log telegraf.Logger `toml:"-"`
5756
kafka.ReadConfig
@@ -200,12 +199,6 @@ func (k *KafkaConsumer) Init() error {
200199
cfg.Consumer.MaxWaitTime = time.Duration(k.ConsumerFetchMaxWait)
201200
}
202201

203-
switch strings.ToLower(k.ConnectionStrategy) {
204-
default:
205-
return fmt.Errorf("invalid connection strategy %q", k.ConnectionStrategy)
206-
case "defer", "startup", "":
207-
}
208-
209202
k.config = cfg
210203

211204
if len(k.TopicRegexps) == 0 {
@@ -231,8 +224,6 @@ func (k *KafkaConsumer) SetParser(parser telegraf.Parser) {
231224
}
232225

233226
func (k *KafkaConsumer) Start(acc telegraf.Accumulator) error {
234-
var err error
235-
236227
// If TopicRegexps is set, add matches to Topics
237228
if len(k.TopicRegexps) > 0 {
238229
if err := k.refreshTopics(); err != nil {
@@ -243,33 +234,19 @@ func (k *KafkaConsumer) Start(acc telegraf.Accumulator) error {
243234
ctx, cancel := context.WithCancel(context.Background())
244235
k.cancel = cancel
245236

246-
if k.ConnectionStrategy != "defer" {
247-
err = k.create()
248-
if err != nil {
249-
return &internal.StartupError{
250-
Err: fmt.Errorf("create consumer: %w", err),
251-
Retry: errors.Is(err, sarama.ErrOutOfBrokers),
252-
}
237+
if err := k.create(); err != nil {
238+
return &internal.StartupError{
239+
Err: fmt.Errorf("create consumer: %w", err),
240+
Retry: errors.Is(err, sarama.ErrOutOfBrokers),
253241
}
254-
k.startErrorAdder(acc)
255242
}
243+
k.startErrorAdder(acc)
256244

257245
// Start consumer goroutine
258246
k.wg.Add(1)
259247
go func() {
260-
var err error
261248
defer k.wg.Done()
262249

263-
if k.consumer == nil {
264-
err = k.create()
265-
if err != nil {
266-
acc.AddError(fmt.Errorf("create consumer async: %w", err))
267-
return
268-
}
269-
}
270-
271-
k.startErrorAdder(acc)
272-
273250
for ctx.Err() == nil {
274251
handler := newConsumerGroupHandler(acc, k.MaxUndeliveredMessages, k.parser, k.Log)
275252
handler.maxMessageLen = k.MaxMessageLen
@@ -300,8 +277,7 @@ func (k *KafkaConsumer) Start(acc telegraf.Accumulator) error {
300277
internal.SleepContext(ctx, reconnectDelay) //nolint:errcheck // ignore returned error as we cannot do anything about it anyway
301278
}
302279
}
303-
err = k.consumer.Close()
304-
if err != nil {
280+
if err := k.consumer.Close(); err != nil {
305281
acc.AddError(fmt.Errorf("close: %w", err))
306282
}
307283
}()

0 commit comments

Comments
 (0)