Skip to content

Commit 5f4cae1

Browse files
committed
feat(migrations): Add migration for inputs.kafka_consumer connection_strategy option (#19121)
(cherry picked from commit 629a223)
1 parent 1bc5328 commit 5f4cae1

7 files changed

Lines changed: 224 additions & 0 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"

0 commit comments

Comments
 (0)