package zabbix import ( "encoding/json" "fmt" "os" "sort" "testing" "time" "github.com/stretchr/testify/require" "github.com/influxdata/telegraf" "github.com/influxdata/telegraf/config" "github.com/influxdata/telegraf/testutil" ) type ( // Operations is an interface to simulate aggregator operations Operations interface{} // OperationAdd is an array of metrics to add to the aggregator OperationAdd []telegraf.Metric // OperationPush simulate a push call to the aggregator OperationPush struct{} // OperationCheck is an array of metrics to check if they are generated by the aggregator OperationCheck []telegraf.Metric // OperationCrossClearIntervalTime is used to simulate a time cross the clear interval OperationCrossClearIntervalTime struct{} ) func TestAddAndPush(t *testing.T) { tests := map[string][]Operations{ "metric without extra tags does not generate LLD metric": { OperationAdd{ testutil.MustMetric( "name", map[string]string{"host": "hostA"}, map[string]interface{}{"value": 1}, time.Now()), }, OperationPush{}, }, "simple Add, Push and check generated LLD metric": { OperationAdd{ testutil.MustMetric( "name", map[string]string{"host": "hostA", "foo": "bar"}, map[string]interface{}{"value": 1}, time.Now()), }, OperationPush{}, OperationCheck{ testutil.MustMetric( lldName, map[string]string{"host": "hostA"}, map[string]interface{}{"name.foo": `{"data":[{"{#FOO}":"bar"}]}`}, time.Now(), ), }, }, "same metric with different tag values": { OperationAdd{ testutil.MustMetric( "name", map[string]string{"host": "hostA", "foo": "bar1"}, map[string]interface{}{"value": 1}, time.Now()), }, OperationAdd{ testutil.MustMetric( "name", map[string]string{"host": "hostA", "foo": "bar2"}, map[string]interface{}{"value": 1}, time.Now()), }, OperationPush{}, OperationCheck{ testutil.MustMetric( lldName, map[string]string{"host": "hostA"}, map[string]interface{}{"name.foo": `{"data":[{"{#FOO}":"bar1"},{"{#FOO}":"bar2"}]}`}, time.Now(), ), }, }, "add two metrics, Push and check generated LLD metric": { OperationAdd{ testutil.MustMetric( "nameA", map[string]string{"host": "hostA", "foo": "bar"}, map[string]interface{}{"value": 1}, time.Now()), testutil.MustMetric( "nameB", map[string]string{"host": "hostA", "foo": "bar"}, map[string]interface{}{"value": 1}, time.Now()), }, OperationPush{}, OperationCheck{ testutil.MustMetric( lldName, map[string]string{"host": "hostA"}, map[string]interface{}{"nameA.foo": `{"data":[{"{#FOO}":"bar"}]}`}, time.Now(), ), testutil.MustMetric( lldName, map[string]string{"host": "hostA"}, map[string]interface{}{"nameB.foo": `{"data":[{"{#FOO}":"bar"}]}`}, time.Now(), ), }, }, "add two similar metrics, one with one more extra tag": { OperationAdd{ testutil.MustMetric( "nameA", map[string]string{"host": "hostA", "foo1": "bar"}, map[string]interface{}{"value": 1}, time.Now()), testutil.MustMetric( "nameA", map[string]string{"host": "hostA", "foo1": "bar", "foo2": "baz"}, map[string]interface{}{"value": 1}, time.Now()), }, OperationPush{}, OperationCheck{ testutil.MustMetric( lldName, map[string]string{"host": "hostA"}, map[string]interface{}{"nameA.foo1": `{"data":[{"{#FOO1}":"bar"}]}`}, time.Now(), ), testutil.MustMetric( lldName, map[string]string{"host": "hostA"}, map[string]interface{}{"nameA.foo1.foo2": `{"data":[{"{#FOO1}":"bar","{#FOO2}":"baz"}]}`}, time.Now(), ), }, }, "same metric several times generate only one LLD": { OperationAdd{ testutil.MustMetric( "name", map[string]string{"host": "hostA", "foo": "bar"}, map[string]interface{}{"value": 1}, time.Now(), ), testutil.MustMetric( "name", map[string]string{"host": "hostA", "foo": "bar"}, map[string]interface{}{"value": 1}, time.Now(), ), testutil.MustMetric( "name", map[string]string{"host": "hostA", "foo": "bar"}, map[string]interface{}{"value": 1}, time.Now(), ), }, OperationPush{}, OperationCheck{ testutil.MustMetric( lldName, map[string]string{"host": "hostA"}, map[string]interface{}{"name.foo": `{"data":[{"{#FOO}":"bar"}]}`}, time.Now(), ), }, }, "same metric several times, with different tag ordering, generate only one LLD": { OperationAdd{ testutil.MustMetric( "name", map[string]string{"host": "hostA", "foo": "bar", "baz": "qux"}, map[string]interface{}{"value": 1}, time.Now(), ), testutil.MustMetric( "name", map[string]string{"host": "hostA", "baz": "qux", "foo": "bar"}, map[string]interface{}{"value": 1}, time.Now(), ), testutil.MustMetric( "name", map[string]string{"baz": "qux", "foo": "bar", "host": "hostA"}, map[string]interface{}{"value": 1}, time.Now(), ), }, OperationPush{}, OperationCheck{ testutil.MustMetric( lldName, map[string]string{"host": "hostA"}, map[string]interface{}{"name.baz.foo": `{"data":[{"{#BAZ}":"qux","{#FOO}":"bar"}]}`}, time.Now(), ), }, }, "after sending correctly an LLD, same tag values does not generate the same LLD": { OperationAdd{testutil.MustMetric( "name", map[string]string{"host": "hostA", "foo": "bar"}, map[string]interface{}{"value": 1}, time.Now(), )}, OperationPush{}, OperationCheck{ testutil.MustMetric( lldName, map[string]string{"host": "hostA"}, map[string]interface{}{"name.foo": `{"data":[{"{#FOO}":"bar"}]}`}, time.Now(), ), }, OperationAdd{testutil.MustMetric( "name", map[string]string{"host": "hostA", "foo": "bar"}, map[string]interface{}{"value": 1}, time.Now(), )}, OperationPush{}, }, "after lld_clear_interval, already seen LLDs could be resend": { OperationAdd{testutil.MustMetric( "name", map[string]string{"host": "hostA", "foo": "bar"}, map[string]interface{}{"value": 1}, time.Now(), )}, OperationPush{}, OperationCheck{ testutil.MustMetric( lldName, map[string]string{"host": "hostA"}, map[string]interface{}{"name.foo": `{"data":[{"{#FOO}":"bar"}]}`}, time.Now(), ), }, OperationAdd{testutil.MustMetric( "name", map[string]string{"host": "hostA", "foo": "bar"}, map[string]interface{}{"value": 1}, time.Now(), )}, OperationPush{}, OperationCrossClearIntervalTime{}, // The clear of the previous LLD seen is done in the next push OperationAdd{testutil.MustMetric( "name", map[string]string{"host": "hostA", "foo": "bar"}, map[string]interface{}{"value": 1}, time.Now(), )}, OperationPush{}, OperationAdd{testutil.MustMetric( "name", map[string]string{"host": "hostA", "foo": "bar"}, map[string]interface{}{"value": 1}, time.Now(), )}, OperationPush{}, OperationCheck{ testutil.MustMetric( lldName, map[string]string{"host": "hostA"}, map[string]interface{}{"name.foo": `{"data":[{"{#FOO}":"bar"}]}`}, time.Now(), ), }, }, "clear interval does not interfere with the send of empty LLDs": { OperationAdd{testutil.MustMetric( "name", map[string]string{"host": "hostA", "foo": "bar"}, map[string]interface{}{"value": 1}, time.Now(), )}, OperationPush{}, OperationCheck{ testutil.MustMetric( lldName, map[string]string{"host": "hostA"}, map[string]interface{}{"name.foo": `{"data":[{"{#FOO}":"bar"}]}`}, time.Now(), ), }, OperationAdd{testutil.MustMetric( "name", map[string]string{"host": "hostA", "foo": "bar"}, map[string]interface{}{"value": 1}, time.Now(), )}, OperationPush{}, // In this interval between push, the metric is not received OperationCrossClearIntervalTime{}, // The clear of the previous LLD seen is done in the next push OperationPush{}, OperationCheck{ testutil.MustMetric( lldName, map[string]string{"host": "hostA"}, map[string]interface{}{"name.foo": `{"data":[]}`}, time.Now(), ), }, }, "one metric changes the value of the tag, it should send the new value and not send and empty lld": { OperationAdd{testutil.MustMetric( "name", map[string]string{"host": "hostA", "foo": "bar1"}, map[string]interface{}{"value": 1}, time.Now(), )}, OperationPush{}, OperationCheck{ testutil.MustMetric( lldName, map[string]string{"host": "hostA"}, map[string]interface{}{"name.foo": `{"data":[{"{#FOO}":"bar1"}]}`}, time.Now(), ), }, OperationAdd{testutil.MustMetric( "name", map[string]string{"host": "hostA", "foo": "bar2"}, map[string]interface{}{"value": 1}, time.Now(), )}, OperationPush{}, OperationCheck{ testutil.MustMetric( lldName, map[string]string{"host": "hostA"}, map[string]interface{}{"name.foo": `{"data":[{"{#FOO}":"bar2"}]}`}, time.Now(), ), }, }, "if one input stop sending metrics, an empty LLD is sent": { OperationAdd{testutil.MustMetric( "name", map[string]string{"host": "hostA", "foo": "bar"}, map[string]interface{}{"value": 1}, time.Now(), )}, OperationPush{}, OperationCheck{ testutil.MustMetric( lldName, map[string]string{"host": "hostA"}, map[string]interface{}{"name.foo": `{"data":[{"{#FOO}":"bar"}]}`}, time.Now(), ), }, OperationPush{}, OperationCheck{testutil.MustMetric( lldName, map[string]string{"host": "hostA"}, map[string]interface{}{"name.foo": `{"data":[]}`}, time.Now(), )}, }, "from two inputs, one stop sending metrics, an empty LLD is sent just for that stopped input": { OperationAdd{ testutil.MustMetric( "name", map[string]string{"host": "hostA", "foo": "bar"}, map[string]interface{}{"value": 1}, time.Now(), ), testutil.MustMetric( "name", map[string]string{"host": "hostB", "foo": "bar"}, map[string]interface{}{"value": 1}, time.Now(), ), }, OperationPush{}, OperationCheck{ testutil.MustMetric( lldName, map[string]string{"host": "hostA"}, map[string]interface{}{"name.foo": `{"data":[{"{#FOO}":"bar"}]}`}, time.Now(), ), testutil.MustMetric( lldName, map[string]string{"host": "hostB"}, map[string]interface{}{"name.foo": `{"data":[{"{#FOO}":"bar"}]}`}, time.Now(), ), }, OperationAdd{ testutil.MustMetric( "name", map[string]string{"host": "hostB", "foo": "bar"}, map[string]interface{}{"value": 1}, time.Now(), ), }, OperationPush{}, OperationCheck{ testutil.MustMetric( lldName, map[string]string{"host": "hostA"}, map[string]interface{}{"name.foo": `{"data":[]}`}, time.Now(), ), }, }, "different hosts sending the same metric should generate different LLDs": { OperationAdd{testutil.MustMetric( "name", map[string]string{"host": "hostA", "foo": "bar"}, map[string]interface{}{"value": 1}, time.Now(), )}, OperationAdd{testutil.MustMetric( "name", map[string]string{"host": "hostB", "foo": "bar"}, map[string]interface{}{"value": 1}, time.Now(), )}, OperationPush{}, OperationCheck{ testutil.MustMetric( lldName, map[string]string{"host": "hostA"}, map[string]interface{}{"name.foo": `{"data":[{"{#FOO}":"bar"}]}`}, time.Now(), ), testutil.MustMetric( lldName, map[string]string{"host": "hostB"}, map[string]interface{}{"name.foo": `{"data":[{"{#FOO}":"bar"}]}`}, time.Now(), ), }, }, "same measurement with different tags should generate different LLDs": { OperationAdd{testutil.MustMetric( "name", map[string]string{"host": "hostA", "foo": "a"}, map[string]interface{}{"value": 1}, time.Now(), )}, OperationAdd{ testutil.MustMetric( "name", map[string]string{"host": "hostA", "foo": "a", "bar": "b"}, map[string]interface{}{"value": 1}, time.Now(), ), }, OperationPush{}, OperationCheck{ testutil.MustMetric( lldName, map[string]string{"host": "hostA"}, map[string]interface{}{"name.foo": `{"data":[{"{#FOO}":"a"}]}`}, time.Now(), ), testutil.MustMetric( lldName, map[string]string{"host": "hostA"}, map[string]interface{}{"name.bar.foo": `{"data":[{"{#BAR}":"b","{#FOO}":"a"}]}`}, time.Now(), ), }, }, "a set with a new combination of tag values already seen should generate a new lld": { OperationAdd{ testutil.MustMetric( "name", map[string]string{"host": "hostA", "foo": "a", "bar": "b"}, map[string]interface{}{"value": 1}, time.Now(), ), }, OperationAdd{ testutil.MustMetric( "name", map[string]string{"host": "hostA", "foo": "x", "bar": "y"}, map[string]interface{}{"value": 1}, time.Now(), ), }, OperationAdd{ testutil.MustMetric( "name", map[string]string{"host": "hostA", "foo": "a", "bar": "y"}, map[string]interface{}{"value": 1}, time.Now(), ), }, OperationPush{}, OperationCheck{ testutil.MustMetric( lldName, map[string]string{"host": "hostA"}, map[string]interface{}{ "name.bar.foo": `{"data":[{"{#BAR}":"b","{#FOO}":"a"},{"{#BAR}":"y","{#FOO}":"a"},{"{#BAR}":"y","{#FOO}":"x"}]}`, }, time.Now(), ), }, }, "same host and metric with and without extra tag": { OperationAdd{ testutil.MustMetric( "name", map[string]string{"host": "hostA", "foo": "a"}, map[string]interface{}{"value": 1}, time.Now(), ), }, OperationAdd{ testutil.MustMetric( "name", map[string]string{"host": "hostA"}, map[string]interface{}{"value": 1}, time.Now(), ), }, OperationPush{}, OperationCheck{ testutil.MustMetric( lldName, map[string]string{"host": "hostA"}, map[string]interface{}{ "name.foo": `{"data":[{"{#FOO}":"a"}]}`, }, time.Now(), ), }, OperationCrossClearIntervalTime{}, OperationPush{}, OperationAdd{ testutil.MustMetric( "name", map[string]string{"host": "hostA"}, map[string]interface{}{"value": 1}, time.Now(), ), }, OperationAdd{ testutil.MustMetric( "name", map[string]string{"host": "hostA", "foo": "a"}, map[string]interface{}{"value": 1}, time.Now(), ), }, OperationPush{}, OperationCheck{ testutil.MustMetric( lldName, map[string]string{"host": "hostA"}, map[string]interface{}{ "name.foo": `{"data":[{"{#FOO}":"a"}]}`, }, time.Now(), ), }, OperationAdd{ testutil.MustMetric( "name", map[string]string{"host": "hostA"}, map[string]interface{}{"value": 1}, time.Now(), ), }, OperationPush{}, // Clean name.foo because it has not been since the last push OperationCheck{ testutil.MustMetric( lldName, map[string]string{"host": "hostA"}, map[string]interface{}{ "name.foo": `{"data":[]}`, }, time.Now(), ), }, }, } for desc, test := range tests { t.Run(desc, func(t *testing.T) { zl := zabbixLLD{ log: testutil.Logger{}, clearInterval: config.Duration(time.Hour), lastClear: time.Now(), hostTag: "host", current: make(map[uint64]lldInfo), } var metrics []telegraf.Metric for _, op := range test { switch o := (op).(type) { case OperationAdd: for _, m := range o { require.NoError(t, zl.Add(m)) } case OperationPush: metrics = zl.Push() case OperationCheck: metrics = sortMetricJSONData(metrics) testutil.RequireMetricsEqual(t, o, metrics, testutil.IgnoreTime(), testutil.SortMetrics()) case OperationCrossClearIntervalTime: // Simulate the time passing by and crossing the clear interval time. // Add an extra millisecond to be sure to cross the interval in the next operation. zl.lastClear = time.Now().Add(-time.Duration(zl.clearInterval)).Add(-time.Millisecond) } } }) } } func TestPush(t *testing.T) { tests := map[string]struct { ReceivedData map[uint64]lldInfo PreviousReceivedData map[uint64]lldInfo Metrics []telegraf.Metric }{ "an empty ReceivedData does not generate any metric": { ReceivedData: map[uint64]lldInfo{}, PreviousReceivedData: map[uint64]lldInfo{}, }, "simple one host with one lld with one set of values": { ReceivedData: map[uint64]lldInfo{ 0: { Hostname: "hostA", Key: "disk.foo", Data: map[uint64]map[string]string{ 1: { "{#FOO}": "bar", }, }, }, }, PreviousReceivedData: map[uint64]lldInfo{}, Metrics: []telegraf.Metric{ testutil.MustMetric(lldName, map[string]string{"host": "hostA"}, map[string]interface{}{"disk.foo": `{"data":[{"{#FOO}":"bar"}]}`}, time.Now(), ), }, }, "one host with one lld with two set of values": { ReceivedData: map[uint64]lldInfo{ 0: { Hostname: "hostA", Key: "disk.foo", Data: map[uint64]map[string]string{ 1: { "{#FOO}": "bar1", }, 2: { "{#FOO}": "bar2", }, }, }, }, PreviousReceivedData: map[uint64]lldInfo{}, Metrics: []telegraf.Metric{ testutil.MustMetric(lldName, map[string]string{"host": "hostA"}, map[string]interface{}{"disk.foo": `{"data":[{"{#FOO}":"bar1"},{"{#FOO}":"bar2"}]}`}, time.Now(), ), }, }, "one host with one lld with one multiset of values": { ReceivedData: map[uint64]lldInfo{ 0: { Hostname: "hostA", Key: "disk.fooA.fooB.fooC", Data: map[uint64]map[string]string{ 1: { "{#FOOA}": "bar1", "{#FOOB}": "bar2", "{#FOOC}": "bar3", }, }, }, }, PreviousReceivedData: map[uint64]lldInfo{}, Metrics: []telegraf.Metric{ testutil.MustMetric(lldName, map[string]string{"host": "hostA"}, map[string]interface{}{"disk.fooA.fooB.fooC": `{"data":[{"{#FOOA}":"bar1","{#FOOB}":"bar2","{#FOOC}":"bar3"}]}`}, time.Now(), ), }, }, "one host with three lld with one set of values, not sorted": { ReceivedData: map[uint64]lldInfo{ 0: { Hostname: "hostA", Key: "disk.foo", Data: map[uint64]map[string]string{ 1: { "{#FOO}": "bar", }, }, }, 1: { Hostname: "hostA", Key: "net.iface", Data: map[uint64]map[string]string{ 1: { "{#IFACE}": "eth0", }, }, }, 2: { Hostname: "hostA", Key: "proc.pid", Data: map[uint64]map[string]string{ 1: { "{#PID}": "1234", }, }, }, }, PreviousReceivedData: map[uint64]lldInfo{}, Metrics: []telegraf.Metric{ testutil.MustMetric(lldName, map[string]string{"host": "hostA"}, map[string]interface{}{"proc.pid": `{"data":[{"{#PID}":"1234"}]}`}, time.Now(), ), testutil.MustMetric(lldName, map[string]string{"host": "hostA"}, map[string]interface{}{"disk.foo": `{"data":[{"{#FOO}":"bar"}]}`}, time.Now(), ), testutil.MustMetric(lldName, map[string]string{"host": "hostA"}, map[string]interface{}{"net.iface": `{"data":[{"{#IFACE}":"eth0"}]}`}, time.Now(), ), }, }, "two host with the same lld with one set of values": { ReceivedData: map[uint64]lldInfo{ 0: { Hostname: "hostA", Key: "disk.foo", Data: map[uint64]map[string]string{ 1: { "{#FOO}": "bar", }, }, }, 1: { Hostname: "hostB", Key: "disk.foo", Data: map[uint64]map[string]string{ 1: { "{#FOO}": "bar", }, }, }, }, PreviousReceivedData: map[uint64]lldInfo{}, Metrics: []telegraf.Metric{ testutil.MustMetric(lldName, map[string]string{"host": "hostA"}, map[string]interface{}{"disk.foo": `{"data":[{"{#FOO}":"bar"}]}`}, time.Now(), ), testutil.MustMetric(lldName, map[string]string{"host": "hostB"}, map[string]interface{}{"disk.foo": `{"data":[{"{#FOO}":"bar"}]}`}, time.Now(), ), }, }, "ignore generating a new lld if it was sent the last time": { ReceivedData: map[uint64]lldInfo{ 2658406801034663970: { Hostname: "hostA", Key: "disk.foo", Data: map[uint64]map[string]string{ 1: { "{#FOO}": "bar", }, }, }, }, PreviousReceivedData: map[uint64]lldInfo{ 2658406801034663970: { Hostname: "hostA", Key: "disk.foo", Data: map[uint64]map[string]string{ 1: { "{#FOO}": "bar", }, }, }, }, }, "send an empty LLD if one metric has stopped being sent": { ReceivedData: map[uint64]lldInfo{}, PreviousReceivedData: map[uint64]lldInfo{ 0: { Hostname: "hostA", Key: "disk.foo", Data: map[uint64]map[string]string{ 1: { "{#FOO}": "bar", }, }, }, }, Metrics: []telegraf.Metric{ testutil.MustMetric(lldName, map[string]string{"host": "hostA"}, map[string]interface{}{"disk.foo": `{"data":[]}`}, time.Now(), ), }, }, } for desc, test := range tests { t.Run(desc, func(t *testing.T) { zl := zabbixLLD{ clearInterval: 4, log: testutil.Logger{}, current: test.ReceivedData, previous: test.PreviousReceivedData, hostTag: "host", } // Hash the previous data for series, info := range zl.previous { info.DataHash = info.hash() zl.previous[series] = info } metrics := zl.Push() // Sort the "data" dict in the metrics values to get always the same order. metrics = sortMetricJSONData(metrics) testutil.RequireMetricsEqual(t, test.Metrics, metrics, testutil.IgnoreTime(), testutil.SortMetrics()) }) } } type MetricValue struct { Data []map[string]string `json:"data"` } // sortMetricJSONData given a list of metrics, if the name is equal to lldName, the JSON data dictionaries are sorted. // This is needed because the order of the JSON data dictionaries is not guaranteed but we need them sorted to compare // them against the expected tests values. This sorting should be done using the keys of the dictionaries. // Example: // // Original metrics: lld,host=foo disk.foo={"data":[{"{#FOO2}":"bar2"},{"{#FOO1}":"bar1"}]} // Sorted metrics: lld,host=foo disk.foo={"data":[{"{#FOO1}":"bar1"},{"{#FOO2}":"bar2"}]} func sortMetricJSONData(metrics []telegraf.Metric) []telegraf.Metric { for _, m := range metrics { if m.Name() == lldName { for _, f := range m.FieldList() { // f is a string with format: '{"data":[{"{#PID}":"1234"}]}' var data MetricValue err := json.Unmarshal([]byte(f.Value.(string)), &data) if err != nil { panic(err) } // Sort data comparing the content as a string sort.Slice(data.Data, func(i, j int) bool { return fmt.Sprintf("%v", data.Data[i]) < fmt.Sprintf("%v", data.Data[j]) }) dataJSON, err := json.Marshal(data) if err != nil { panic(err) } f.Value = string(dataJSON) } } } return metrics } func TestAdd(t *testing.T) { hostname, err := os.Hostname() require.NoError(t, err) tests := map[string]struct { Metrics []telegraf.Metric Current map[uint64]lldInfo }{ "metric without tags is ignored": { Metrics: []telegraf.Metric{ testutil.MustMetric("disk", map[string]string{}, map[string]interface{}{"a": 0}, time.Now()), }, Current: map[uint64]lldInfo{}, }, "metric with only the host tag is not used for LLD": { Metrics: []telegraf.Metric{ testutil.MustMetric( "disk", map[string]string{"host": "bar"}, map[string]interface{}{"a": 0}, time.Now(), ), }, Current: map[uint64]lldInfo{}, }, "add one metric with one tag and not host tag, use the system hostname": { Metrics: []telegraf.Metric{ testutil.MustMetric( "disk", map[string]string{"foo": "bar"}, map[string]interface{}{"a": 0}, time.Now(), ), }, Current: map[uint64]lldInfo{ 1: { Hostname: hostname, Key: "disk.foo", Data: map[uint64]map[string]string{ 2011740591878200733: { "{#FOO}": "bar", }, }, }, }, }, "add one metric with one extra tag": { Metrics: []telegraf.Metric{ testutil.MustMetric( "disk", map[string]string{"host": "bar", "foo": "bar"}, map[string]interface{}{"a": 0}, time.Now(), ), }, Current: map[uint64]lldInfo{ 1: { Hostname: "bar", Key: "disk.foo", Data: map[uint64]map[string]string{ 13756738031738276742: { "{#FOO}": "bar", }, }, }, }, }, "same metric with different field values is only stored once": { Metrics: []telegraf.Metric{ testutil.MustMetric( "disk", map[string]string{"host": "bar", "foo": "bar"}, map[string]interface{}{"a": 0}, time.Now(), ), testutil.MustMetric( "disk", map[string]string{"host": "bar", "foo": "bar"}, map[string]interface{}{"a": 999}, time.Now(), ), }, Current: map[uint64]lldInfo{ 1: { Hostname: "bar", Key: "disk.foo", Data: map[uint64]map[string]string{ 13756738031738276742: { "{#FOO}": "bar", }, }, }, }, }, "for the same measurement and tags, the different combinations of tag values are stored under the same key": { Metrics: []telegraf.Metric{ testutil.MustMetric( "disk", map[string]string{"host": "bar", "foo": "bar1"}, map[string]interface{}{"a": 0}, time.Now(), ), testutil.MustMetric( "disk", map[string]string{"host": "bar", "foo": "bar2"}, map[string]interface{}{"a": 0}, time.Now(), ), }, Current: map[uint64]lldInfo{ 1: { Hostname: "bar", Key: "disk.foo", Data: map[uint64]map[string]string{ 9541037171803204811: { "{#FOO}": "bar1", }, 10966311568236988310: { "{#FOO}": "bar2", }, }, }, }, }, "same measurement and tags for different hosts are stored in different keys": { Metrics: []telegraf.Metric{ testutil.MustMetric( "disk", map[string]string{"host": "barA", "foo": "bar"}, map[string]interface{}{"a": 0}, time.Now(), ), testutil.MustMetric( "disk", map[string]string{"host": "barB", "foo": "bar"}, map[string]interface{}{"a": 0}, time.Now(), ), }, Current: map[uint64]lldInfo{ 1: { Hostname: "barA", Key: "disk.foo", Data: map[uint64]map[string]string{ 4916699111010086803: { "{#FOO}": "bar", }, }, }, 2: { Hostname: "barB", Key: "disk.foo", Data: map[uint64]map[string]string{ 4917655686126441148: { "{#FOO}": "bar", }, }, }, }, }, "different number of tags for the same measurement are stored in different keys": { Metrics: []telegraf.Metric{ testutil.MustMetric( "disk", map[string]string{"host": "bar", "foo1": "bar", "foo2": "bar"}, map[string]interface{}{"a": 0}, time.Now(), ), testutil.MustMetric( "disk", map[string]string{"host": "bar", "foo1": "bar"}, map[string]interface{}{"a": 0}, time.Now(), ), }, Current: map[uint64]lldInfo{ 1: { Hostname: "bar", Key: "disk.foo1.foo2", Data: map[uint64]map[string]string{ 12473238139685120014: { "{#FOO1}": "bar", "{#FOO2}": "bar", }, }, }, 2: { Hostname: "bar", Key: "disk.foo1", Data: map[uint64]map[string]string{ 4193955122073793785: { "{#FOO1}": "bar", }, }, }, }, }, } for desc, test := range tests { t.Run(desc, func(t *testing.T) { zl := zabbixLLD{ log: testutil.Logger{}, clearInterval: config.Duration(time.Hour), lastClear: time.Now(), hostTag: "host", current: make(map[uint64]lldInfo), } for _, m := range test.Metrics { require.NoError(t, zl.Add(m)) } // Calculate series ID for the test data. // Metric hashes could not be calculated because we don't have enough information. for id, info := range test.Current { calculatedID := lldSeriesID(info.Hostname, info.Key) if id == calculatedID { continue } test.Current[calculatedID] = info // Drop old ID delete(test.Current, id) } require.Equal(t, test.Current, zl.current) }) } }