package config import ( "fmt" "net/http" "net/http/httptest" "os" "runtime" "strings" "testing" "time" "github.com/stretchr/testify/require" "github.com/influxdata/telegraf" "github.com/influxdata/telegraf/models" "github.com/influxdata/telegraf/plugins/common/tls" "github.com/influxdata/telegraf/plugins/inputs" "github.com/influxdata/telegraf/plugins/outputs" "github.com/influxdata/telegraf/plugins/parsers" ) func TestConfig_LoadSingleInputWithEnvVars(t *testing.T) { c := NewConfig() require.NoError(t, os.Setenv("MY_TEST_SERVER", "192.168.1.1")) require.NoError(t, os.Setenv("TEST_INTERVAL", "10s")) c.LoadConfig("./testdata/single_plugin_env_vars.toml") input := inputs.Inputs["memcached"]().(*MockupInputPlugin) input.Servers = []string{"192.168.1.1"} filter := models.Filter{ NameDrop: []string{"metricname2"}, NamePass: []string{"metricname1", "ip_192.168.1.1_name"}, FieldDrop: []string{"other", "stuff"}, FieldPass: []string{"some", "strings"}, TagDrop: []models.TagFilter{ { Name: "badtag", Filter: []string{"othertag"}, }, }, TagPass: []models.TagFilter{ { Name: "goodtag", Filter: []string{"mytag"}, }, }, } require.NoError(t, filter.Compile()) inputConfig := &models.InputConfig{ Name: "memcached", Filter: filter, Interval: 10 * time.Second, } inputConfig.Tags = make(map[string]string) // Ignore Log and Parser c.Inputs[0].Input.(*MockupInputPlugin).Log = nil c.Inputs[0].Input.(*MockupInputPlugin).parser = nil require.Equal(t, input, c.Inputs[0].Input, "Testdata did not produce a correct mockup struct.") require.Equal(t, inputConfig, c.Inputs[0].Config, "Testdata did not produce correct input metadata.") } func TestConfig_LoadSingleInput(t *testing.T) { c := NewConfig() c.LoadConfig("./testdata/single_plugin.toml") input := inputs.Inputs["memcached"]().(*MockupInputPlugin) input.Servers = []string{"localhost"} filter := models.Filter{ NameDrop: []string{"metricname2"}, NamePass: []string{"metricname1"}, FieldDrop: []string{"other", "stuff"}, FieldPass: []string{"some", "strings"}, TagDrop: []models.TagFilter{ { Name: "badtag", Filter: []string{"othertag"}, }, }, TagPass: []models.TagFilter{ { Name: "goodtag", Filter: []string{"mytag"}, }, }, } require.NoError(t, filter.Compile()) inputConfig := &models.InputConfig{ Name: "memcached", Filter: filter, Interval: 5 * time.Second, } inputConfig.Tags = make(map[string]string) // Ignore Log and Parser c.Inputs[0].Input.(*MockupInputPlugin).Log = nil c.Inputs[0].Input.(*MockupInputPlugin).parser = nil require.Equal(t, input, c.Inputs[0].Input, "Testdata did not produce a correct memcached struct.") require.Equal(t, inputConfig, c.Inputs[0].Config, "Testdata did not produce correct memcached metadata.") } func TestConfig_LoadDirectory(t *testing.T) { c := NewConfig() require.NoError(t, c.LoadConfig("./testdata/single_plugin.toml")) require.NoError(t, c.LoadDirectory("./testdata/subconfig")) // Create the expected data expectedPlugins := make([]*MockupInputPlugin, 4) expectedConfigs := make([]*models.InputConfig, 4) expectedPlugins[0] = inputs.Inputs["memcached"]().(*MockupInputPlugin) expectedPlugins[0].Servers = []string{"localhost"} filterMockup := models.Filter{ NameDrop: []string{"metricname2"}, NamePass: []string{"metricname1"}, FieldDrop: []string{"other", "stuff"}, FieldPass: []string{"some", "strings"}, TagDrop: []models.TagFilter{ { Name: "badtag", Filter: []string{"othertag"}, }, }, TagPass: []models.TagFilter{ { Name: "goodtag", Filter: []string{"mytag"}, }, }, } require.NoError(t, filterMockup.Compile()) expectedConfigs[0] = &models.InputConfig{ Name: "memcached", Filter: filterMockup, Interval: 5 * time.Second, } expectedConfigs[0].Tags = make(map[string]string) expectedPlugins[1] = inputs.Inputs["exec"]().(*MockupInputPlugin) parserConfig := &parsers.Config{ MetricName: "exec", DataFormat: "json", JSONStrict: true, } p, err := parsers.NewParser(parserConfig) require.NoError(t, err) // Inject logger to have proper struct for comparison models.SetLoggerOnPlugin(p, models.NewLogger("parsers", parserConfig.DataFormat, parserConfig.MetricName)) expectedPlugins[1].SetParser(p) expectedPlugins[1].Command = "/usr/bin/myothercollector --foo=bar" expectedConfigs[1] = &models.InputConfig{ Name: "exec", MeasurementSuffix: "_myothercollector", } expectedConfigs[1].Tags = make(map[string]string) expectedPlugins[2] = inputs.Inputs["memcached"]().(*MockupInputPlugin) expectedPlugins[2].Servers = []string{"192.168.1.1"} filterMemcached := models.Filter{ NameDrop: []string{"metricname2"}, NamePass: []string{"metricname1"}, FieldDrop: []string{"other", "stuff"}, FieldPass: []string{"some", "strings"}, TagDrop: []models.TagFilter{ { Name: "badtag", Filter: []string{"othertag"}, }, }, TagPass: []models.TagFilter{ { Name: "goodtag", Filter: []string{"mytag"}, }, }, } require.NoError(t, filterMemcached.Compile()) expectedConfigs[2] = &models.InputConfig{ Name: "memcached", Filter: filterMemcached, Interval: 5 * time.Second, } expectedConfigs[2].Tags = make(map[string]string) expectedPlugins[3] = inputs.Inputs["procstat"]().(*MockupInputPlugin) expectedPlugins[3].PidFile = "/var/run/grafana-server.pid" expectedConfigs[3] = &models.InputConfig{Name: "procstat"} expectedConfigs[3].Tags = make(map[string]string) // Check the generated plugins require.Len(t, c.Inputs, len(expectedPlugins)) require.Len(t, c.Inputs, len(expectedConfigs)) for i, plugin := range c.Inputs { input := plugin.Input.(*MockupInputPlugin) // Check the logger and ignore it for comparison require.NotNil(t, input.Log) input.Log = nil // Ignore the parser if not expected if expectedPlugins[i].parser == nil { input.parser = nil } require.Equalf(t, expectedPlugins[i], plugin.Input, "Plugin %d: incorrect struct produced", i) require.Equalf(t, expectedConfigs[i], plugin.Config, "Plugin %d: incorrect config produced", i) } } func TestConfig_LoadSpecialTypes(t *testing.T) { c := NewConfig() require.NoError(t, c.LoadConfig("./testdata/special_types.toml")) require.Len(t, c.Inputs, 1) input, ok := c.Inputs[0].Input.(*MockupInputPlugin) require.True(t, ok) // Tests telegraf duration parsing. require.Equal(t, Duration(time.Second), input.WriteTimeout) // Tests telegraf size parsing. require.Equal(t, Size(1024*1024), input.MaxBodySize) // Tests toml multiline basic strings. require.Equal(t, "/path/to/my/cert", strings.TrimRight(input.TLSCert, "\r\n")) } func TestConfig_FieldNotDefined(t *testing.T) { c := NewConfig() err := c.LoadConfig("./testdata/invalid_field.toml") require.Error(t, err, "invalid field name") require.Equal(t, "Error loading config file ./testdata/invalid_field.toml: plugin inputs.http_listener_v2: line 1: configuration specified the fields [\"not_a_field\"], but they weren't used", err.Error()) } func TestConfig_WrongFieldType(t *testing.T) { c := NewConfig() err := c.LoadConfig("./testdata/wrong_field_type.toml") require.Error(t, err, "invalid field type") require.Equal(t, "Error loading config file ./testdata/wrong_field_type.toml: error parsing http_listener_v2, line 2: (config.MockupInputPlugin.Port) cannot unmarshal TOML string into int", err.Error()) c = NewConfig() err = c.LoadConfig("./testdata/wrong_field_type2.toml") require.Error(t, err, "invalid field type2") require.Equal(t, "Error loading config file ./testdata/wrong_field_type2.toml: error parsing http_listener_v2, line 2: (config.MockupInputPlugin.Methods) cannot unmarshal TOML string into []string", err.Error()) } func TestConfig_InlineTables(t *testing.T) { // #4098 c := NewConfig() require.NoError(t, c.LoadConfig("./testdata/inline_table.toml")) require.Len(t, c.Outputs, 2) output, ok := c.Outputs[1].Output.(*MockupOuputPlugin) require.True(t, ok) require.Equal(t, map[string]string{"Authorization": "Token $TOKEN", "Content-Type": "application/json"}, output.Headers) require.Equal(t, []string{"org_id"}, c.Outputs[0].Config.Filter.TagInclude) } func TestConfig_SliceComment(t *testing.T) { t.Skipf("Skipping until #3642 is resolved") c := NewConfig() require.NoError(t, c.LoadConfig("./testdata/slice_comment.toml")) require.Len(t, c.Outputs, 1) output, ok := c.Outputs[0].Output.(*MockupOuputPlugin) require.True(t, ok) require.Equal(t, []string{"test"}, output.Scopes) } func TestConfig_BadOrdering(t *testing.T) { // #3444: when not using inline tables, care has to be taken so subsequent configuration // doesn't become part of the table. This is not a bug, but TOML syntax. c := NewConfig() err := c.LoadConfig("./testdata/non_slice_slice.toml") require.Error(t, err, "bad ordering") require.Equal(t, "Error loading config file ./testdata/non_slice_slice.toml: error parsing http array, line 4: cannot unmarshal TOML array into string (need slice)", err.Error()) } func TestConfig_AzureMonitorNamespacePrefix(t *testing.T) { // #8256 Cannot use empty string as the namespace prefix c := NewConfig() require.NoError(t, c.LoadConfig("./testdata/azure_monitor.toml")) require.Len(t, c.Outputs, 2) expectedPrefix := []string{"Telegraf/", ""} for i, plugin := range c.Outputs { output, ok := plugin.Output.(*MockupOuputPlugin) require.True(t, ok) require.Equal(t, expectedPrefix[i], output.NamespacePrefix) } } func TestConfig_URLRetries3Fails(t *testing.T) { httpLoadConfigRetryInterval = 0 * time.Second responseCounter := 0 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotFound) responseCounter++ })) defer ts.Close() expected := fmt.Sprintf("Error loading config file %s: Retry 3 of 3 failed to retrieve remote config: 404 Not Found", ts.URL) c := NewConfig() err := c.LoadConfig(ts.URL) require.Error(t, err) require.Equal(t, expected, err.Error()) require.Equal(t, 4, responseCounter) } func TestConfig_URLRetries3FailsThenPasses(t *testing.T) { httpLoadConfigRetryInterval = 0 * time.Second responseCounter := 0 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if responseCounter <= 2 { w.WriteHeader(http.StatusNotFound) } else { w.WriteHeader(http.StatusOK) } responseCounter++ })) defer ts.Close() c := NewConfig() require.NoError(t, c.LoadConfig(ts.URL)) require.Equal(t, 4, responseCounter) } func TestConfig_getDefaultConfigPathFromEnvURL(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) })) defer ts.Close() c := NewConfig() err := os.Setenv("TELEGRAF_CONFIG_PATH", ts.URL) require.NoError(t, err) configPath, err := getDefaultConfigPath() require.NoError(t, err) require.Equal(t, ts.URL, configPath) err = c.LoadConfig("") require.NoError(t, err) } func TestConfig_URLLikeFileName(t *testing.T) { c := NewConfig() err := c.LoadConfig("http:##www.example.com.conf") require.Error(t, err) if runtime.GOOS == "windows" { // The error file not found error message is different on windows require.Equal(t, "Error loading config file http:##www.example.com.conf: open http:##www.example.com.conf: The system cannot find the file specified.", err.Error()) } else { require.Equal(t, "Error loading config file http:##www.example.com.conf: open http:##www.example.com.conf: no such file or directory", err.Error()) } } /*** Mockup INPUT plugin for testing to avoid cyclic dependencies ***/ type MockupInputPlugin struct { Servers []string `toml:"servers"` Methods []string `toml:"methods"` Timeout Duration `toml:"timeout"` ReadTimeout Duration `toml:"read_timeout"` WriteTimeout Duration `toml:"write_timeout"` MaxBodySize Size `toml:"max_body_size"` Port int `toml:"port"` Command string PidFile string Log telegraf.Logger `toml:"-"` tls.ServerConfig parser parsers.Parser } func (m *MockupInputPlugin) SampleConfig() string { return "Mockup test intput plugin" } func (m *MockupInputPlugin) Description() string { return "Mockup test intput plugin" } func (m *MockupInputPlugin) Gather(acc telegraf.Accumulator) error { return nil } func (m *MockupInputPlugin) SetParser(parser parsers.Parser) { m.parser = parser } /*** Mockup OUTPUT plugin for testing to avoid cyclic dependencies ***/ type MockupOuputPlugin struct { URL string `toml:"url"` Headers map[string]string `toml:"headers"` Scopes []string `toml:"scopes"` NamespacePrefix string `toml:"namespace_prefix"` Log telegraf.Logger `toml:"-"` tls.ClientConfig } func (m *MockupOuputPlugin) Connect() error { return nil } func (m *MockupOuputPlugin) Close() error { return nil } func (m *MockupOuputPlugin) Description() string { return "Mockup test output plugin" } func (m *MockupOuputPlugin) SampleConfig() string { return "Mockup test output plugin" } func (m *MockupOuputPlugin) Write(metrics []telegraf.Metric) error { return nil } // Register the mockup plugin on loading func init() { // Register the mockup input plugin for the required names inputs.Add("exec", func() telegraf.Input { return &MockupInputPlugin{Timeout: Duration(time.Second * 5)} }) inputs.Add("http_listener_v2", func() telegraf.Input { return &MockupInputPlugin{} }) inputs.Add("memcached", func() telegraf.Input { return &MockupInputPlugin{} }) inputs.Add("procstat", func() telegraf.Input { return &MockupInputPlugin{} }) // Register the mockup output plugin for the required names outputs.Add("azure_monitor", func() telegraf.Output { return &MockupOuputPlugin{NamespacePrefix: "Telegraf/"} }) outputs.Add("http", func() telegraf.Output { return &MockupOuputPlugin{} }) }