package collectd import ( "context" "testing" "time" "collectd.org/api" "collectd.org/network" "github.com/stretchr/testify/require" "github.com/influxdata/telegraf" "github.com/influxdata/telegraf/metric" "github.com/influxdata/telegraf/testutil" ) type AuthMap struct { Passwd map[string]string } func (p *AuthMap) Password(user string) (string, error) { return p.Passwd[user], nil } type metricData struct { name string tags map[string]string fields map[string]interface{} } type testCase struct { vl []api.ValueList expected []metricData } var singleMetric = testCase{ []api.ValueList{ { Identifier: api.Identifier{ Host: "xyzzy", Plugin: "cpu", PluginInstance: "1", Type: "cpu", TypeInstance: "user", }, Values: []api.Value{ api.Counter(42), }, DSNames: []string(nil), }, }, []metricData{ { "cpu_value", map[string]string{ "type_instance": "user", "host": "xyzzy", "instance": "1", "type": "cpu", }, map[string]interface{}{ "value": float64(42), }, }, }, } var multiMetric = testCase{ []api.ValueList{ { Identifier: api.Identifier{ Host: "xyzzy", Plugin: "cpu", PluginInstance: "0", Type: "cpu", TypeInstance: "user", }, Values: []api.Value{ api.Derive(42), api.Gauge(42), }, DSNames: []string{"t1", "t2"}, }, }, []metricData{ { "cpu_0", map[string]string{ "type_instance": "user", "host": "xyzzy", "instance": "0", "type": "cpu", }, map[string]interface{}{ "value": float64(42), }, }, { "cpu_1", map[string]string{ "type_instance": "user", "host": "xyzzy", "instance": "0", "type": "cpu", }, map[string]interface{}{ "value": float64(42), }, }, }, } func TestNewCollectdParser(t *testing.T) { parser := Parser{ ParseMultiValue: "join", } require.NoError(t, parser.Init()) require.Equal(t, network.None, parser.popts.SecurityLevel) require.NotNil(t, parser.popts.PasswordLookup) require.Nil(t, parser.popts.TypesDB) } func TestParse(t *testing.T) { cases := []testCase{singleMetric, multiMetric} for _, tc := range cases { buf, err := writeValueList(t.Context(), tc.vl) require.NoError(t, err) bytes, err := buf.Bytes() require.NoError(t, err) parser := &Parser{} require.NoError(t, parser.Init()) metrics, err := parser.Parse(bytes) require.NoError(t, err) assertEqualMetrics(t, tc.expected, metrics) } } func TestParseMultiValueSplit(t *testing.T) { buf, err := writeValueList(t.Context(), multiMetric.vl) require.NoError(t, err) bytes, err := buf.Bytes() require.NoError(t, err) parser := &Parser{ParseMultiValue: "split"} require.NoError(t, parser.Init()) metrics, err := parser.Parse(bytes) require.NoError(t, err) require.Len(t, metrics, 2) } func TestParseMultiValueJoin(t *testing.T) { buf, err := writeValueList(t.Context(), multiMetric.vl) require.NoError(t, err) bytes, err := buf.Bytes() require.NoError(t, err) parser := &Parser{ParseMultiValue: "join"} require.NoError(t, parser.Init()) metrics, err := parser.Parse(bytes) require.NoError(t, err) require.Len(t, metrics, 1) } func TestParse_DefaultTags(t *testing.T) { buf, err := writeValueList(t.Context(), singleMetric.vl) require.NoError(t, err) bytes, err := buf.Bytes() require.NoError(t, err) parser := &Parser{} require.NoError(t, parser.Init()) parser.SetDefaultTags(map[string]string{ "foo": "bar", }) require.NoError(t, err) metrics, err := parser.Parse(bytes) require.NoError(t, err) require.Equal(t, "bar", metrics[0].Tags()["foo"]) } func TestParse_SignSecurityLevel(t *testing.T) { parser := &Parser{ SecurityLevel: "sign", AuthFile: "testdata/authfile", } require.NoError(t, parser.Init()) // Signed data buf, err := writeValueList(t.Context(), singleMetric.vl) require.NoError(t, err) buf.Sign("user0", "bar") bytes, err := buf.Bytes() require.NoError(t, err) metrics, err := parser.Parse(bytes) require.NoError(t, err) assertEqualMetrics(t, singleMetric.expected, metrics) // Encrypted data buf, err = writeValueList(t.Context(), singleMetric.vl) require.NoError(t, err) buf.Encrypt("user0", "bar") bytes, err = buf.Bytes() require.NoError(t, err) metrics, err = parser.Parse(bytes) require.NoError(t, err) assertEqualMetrics(t, singleMetric.expected, metrics) // Plain text data skipped buf, err = writeValueList(t.Context(), singleMetric.vl) require.NoError(t, err) bytes, err = buf.Bytes() require.NoError(t, err) metrics, err = parser.Parse(bytes) require.NoError(t, err) require.Empty(t, metrics) // Wrong password error buf, err = writeValueList(t.Context(), singleMetric.vl) require.NoError(t, err) buf.Sign("x", "y") bytes, err = buf.Bytes() require.NoError(t, err) _, err = parser.Parse(bytes) require.Error(t, err) } func TestParse_EncryptSecurityLevel(t *testing.T) { parser := &Parser{ SecurityLevel: "encrypt", AuthFile: "testdata/authfile", } require.NoError(t, parser.Init()) // Signed data skipped buf, err := writeValueList(t.Context(), singleMetric.vl) require.NoError(t, err) buf.Sign("user0", "bar") bytes, err := buf.Bytes() require.NoError(t, err) metrics, err := parser.Parse(bytes) require.NoError(t, err) require.Empty(t, metrics) // Encrypted data buf, err = writeValueList(t.Context(), singleMetric.vl) require.NoError(t, err) buf.Encrypt("user0", "bar") bytes, err = buf.Bytes() require.NoError(t, err) metrics, err = parser.Parse(bytes) require.NoError(t, err) assertEqualMetrics(t, singleMetric.expected, metrics) // Plain text data skipped buf, err = writeValueList(t.Context(), singleMetric.vl) require.NoError(t, err) bytes, err = buf.Bytes() require.NoError(t, err) metrics, err = parser.Parse(bytes) require.NoError(t, err) require.Empty(t, metrics) // Wrong password error buf, err = writeValueList(t.Context(), singleMetric.vl) require.NoError(t, err) buf.Sign("x", "y") bytes, err = buf.Bytes() require.NoError(t, err) _, err = parser.Parse(bytes) require.Error(t, err) } func TestParseLine(t *testing.T) { buf, err := writeValueList(t.Context(), singleMetric.vl) require.NoError(t, err) bytes, err := buf.Bytes() require.NoError(t, err) parser := Parser{ ParseMultiValue: "split", } require.NoError(t, parser.Init()) m, err := parser.ParseLine(string(bytes)) require.NoError(t, err) assertEqualMetrics(t, singleMetric.expected, []telegraf.Metric{m}) } func writeValueList(testContext context.Context, valueLists []api.ValueList) (*network.Buffer, error) { buffer := network.NewBuffer(0) for i := range valueLists { err := buffer.Write(testContext, &valueLists[i]) if err != nil { return nil, err } } return buffer, nil } func assertEqualMetrics(t *testing.T, expected []metricData, received []telegraf.Metric) { require.Len(t, received, len(expected)) for i, m := range received { require.Equal(t, expected[i].name, m.Name()) require.Equal(t, expected[i].tags, m.Tags()) require.Equal(t, expected[i].fields, m.Fields()) } } var benchmarkData = []api.ValueList{ { Identifier: api.Identifier{ Host: "xyzzy", Plugin: "cpu", PluginInstance: "1", Type: "cpu", TypeInstance: "user", }, Values: []api.Value{ api.Counter(4), }, DSNames: []string(nil), }, { Identifier: api.Identifier{ Host: "xyzzy", Plugin: "cpu", PluginInstance: "2", Type: "cpu", TypeInstance: "user", }, Values: []api.Value{ api.Counter(5), }, DSNames: []string(nil), }, } func TestBenchmarkData(t *testing.T) { expected := []telegraf.Metric{ metric.New( "cpu_value", map[string]string{ "host": "xyzzy", "instance": "1", "type": "cpu", "type_instance": "user", }, map[string]interface{}{ "value": 4.0, }, time.Unix(0, 0), ), metric.New( "cpu_value", map[string]string{ "host": "xyzzy", "instance": "2", "type": "cpu", "type_instance": "user", }, map[string]interface{}{ "value": 5.0, }, time.Unix(0, 0), ), } buf, err := writeValueList(t.Context(), benchmarkData) require.NoError(t, err) bytes, err := buf.Bytes() require.NoError(t, err) parser := &Parser{} require.NoError(t, parser.Init()) actual, err := parser.Parse(bytes) require.NoError(t, err) testutil.RequireMetricsEqual(t, expected, actual, testutil.IgnoreTime(), testutil.SortMetrics()) } func BenchmarkParsing(b *testing.B) { buf, err := writeValueList(b.Context(), benchmarkData) require.NoError(b, err) bytes, err := buf.Bytes() require.NoError(b, err) parser := &Parser{} require.NoError(b, parser.Init()) b.ResetTimer() for n := 0; n < b.N; n++ { //nolint:errcheck // Benchmarking so skip the error check to avoid the unnecessary operations parser.Parse(bytes) } }