921 lines
24 KiB
Go
921 lines
24 KiB
Go
package zabbix
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net"
|
|
"os"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/influxdata/telegraf"
|
|
"github.com/influxdata/telegraf/config"
|
|
"github.com/influxdata/telegraf/testutil"
|
|
)
|
|
|
|
type zabbixRequestData struct {
|
|
Host string `json:"host"`
|
|
Key string `json:"key"`
|
|
Value string `json:"value"`
|
|
Clock int64 `json:"clock"`
|
|
}
|
|
|
|
type zabbixRequest struct {
|
|
Request string `json:"request"`
|
|
Data []zabbixRequestData `json:"data"`
|
|
Clock int `json:"clock"`
|
|
Host string `json:"host"`
|
|
HostMetadata string `json:"host_metadata"`
|
|
}
|
|
|
|
type zabbixLLDValue struct {
|
|
Data []map[string]string `json:"data"`
|
|
}
|
|
|
|
func TestZabbix(t *testing.T) {
|
|
hostname, err := os.Hostname()
|
|
require.NoError(t, err)
|
|
|
|
tests := map[string]struct {
|
|
KeyPrefix string
|
|
AgentActive bool
|
|
SkipMeasurementPrefix bool
|
|
telegrafMetrics []telegraf.Metric
|
|
zabbixMetrics []zabbixRequestData
|
|
}{
|
|
"send one metric with one field and no extra tags, generates one zabbix metric": {
|
|
telegrafMetrics: []telegraf.Metric{
|
|
testutil.MustMetric("name",
|
|
map[string]string{
|
|
"host": "hostname",
|
|
},
|
|
map[string]interface{}{
|
|
"value": int64(0),
|
|
},
|
|
time.Unix(1522082244, 0),
|
|
),
|
|
},
|
|
zabbixMetrics: []zabbixRequestData{
|
|
{
|
|
Host: "hostname",
|
|
Key: "name.value",
|
|
Value: "0",
|
|
Clock: 1522082244,
|
|
},
|
|
},
|
|
},
|
|
"string values representing a float number should be sent in the exact same format": {
|
|
telegrafMetrics: []telegraf.Metric{
|
|
testutil.MustMetric("name",
|
|
map[string]string{
|
|
"host": "hostname",
|
|
},
|
|
map[string]interface{}{
|
|
"value": "3.1415",
|
|
},
|
|
time.Unix(1522082244, 0),
|
|
),
|
|
},
|
|
zabbixMetrics: []zabbixRequestData{
|
|
{
|
|
Host: "hostname",
|
|
Key: "name.value",
|
|
Value: "3.1415",
|
|
Clock: 1522082244,
|
|
},
|
|
},
|
|
},
|
|
"send one metric with one string field and no extra tags, generates one zabbix metric": {
|
|
telegrafMetrics: []telegraf.Metric{
|
|
testutil.MustMetric("name",
|
|
map[string]string{
|
|
"host": "hostname",
|
|
},
|
|
map[string]interface{}{
|
|
"value": "some value",
|
|
},
|
|
time.Unix(1522082244, 0),
|
|
),
|
|
},
|
|
zabbixMetrics: []zabbixRequestData{
|
|
{
|
|
Host: "hostname",
|
|
Key: "name.value",
|
|
Value: "some value",
|
|
Clock: 1522082244,
|
|
},
|
|
},
|
|
},
|
|
"boolean values are converted to 1 (true) or 0 (false)": {
|
|
telegrafMetrics: []telegraf.Metric{
|
|
testutil.MustMetric("name",
|
|
map[string]string{
|
|
"host": "hostname",
|
|
},
|
|
map[string]interface{}{
|
|
"valueTrue": true,
|
|
"valueFalse": false,
|
|
},
|
|
time.Unix(1522082244, 0),
|
|
),
|
|
},
|
|
zabbixMetrics: []zabbixRequestData{
|
|
{
|
|
Host: "hostname",
|
|
Key: "name.valueTrue",
|
|
Value: "true",
|
|
Clock: 1522082244,
|
|
},
|
|
{
|
|
Host: "hostname",
|
|
Key: "name.valueFalse",
|
|
Value: "false",
|
|
Clock: 1522082244,
|
|
},
|
|
},
|
|
},
|
|
"invalid value data is ignored and not sent": {
|
|
telegrafMetrics: []telegraf.Metric{
|
|
testutil.MustMetric("name",
|
|
map[string]string{
|
|
"host": "hostname",
|
|
},
|
|
map[string]interface{}{
|
|
"value": []int{1, 2},
|
|
},
|
|
time.Unix(1522082244, 0),
|
|
),
|
|
},
|
|
zabbixMetrics: []zabbixRequestData{},
|
|
},
|
|
"metrics without host tag use the system hostname": {
|
|
telegrafMetrics: []telegraf.Metric{
|
|
testutil.MustMetric("name",
|
|
map[string]string{},
|
|
map[string]interface{}{
|
|
"value": "x",
|
|
},
|
|
time.Unix(1522082244, 0),
|
|
),
|
|
},
|
|
zabbixMetrics: []zabbixRequestData{
|
|
{
|
|
Host: hostname,
|
|
Key: "name.value",
|
|
Value: "x",
|
|
Clock: 1522082244,
|
|
},
|
|
},
|
|
},
|
|
"send one metric with extra tags, zabbix metric should be generated with a parameter": {
|
|
telegrafMetrics: []telegraf.Metric{
|
|
testutil.MustMetric("name",
|
|
map[string]string{
|
|
"host": "hostname",
|
|
"foo": "bar",
|
|
},
|
|
map[string]interface{}{
|
|
"value": int64(0),
|
|
},
|
|
time.Unix(1522082244, 0),
|
|
),
|
|
},
|
|
zabbixMetrics: []zabbixRequestData{
|
|
{
|
|
Host: "hostname",
|
|
Key: "name.value[bar]",
|
|
Value: "0",
|
|
Clock: 1522082244,
|
|
},
|
|
},
|
|
},
|
|
"send one metric with two extra tags, zabbix parameters should be alfabetically orderer": {
|
|
telegrafMetrics: []telegraf.Metric{
|
|
testutil.MustMetric("name",
|
|
map[string]string{
|
|
"host": "hostname",
|
|
"zparam": "last",
|
|
"aparam": "first",
|
|
},
|
|
map[string]interface{}{
|
|
"value": int64(0),
|
|
},
|
|
time.Unix(1522082244, 0),
|
|
),
|
|
},
|
|
zabbixMetrics: []zabbixRequestData{
|
|
{
|
|
Host: "hostname",
|
|
Key: "name.value[first,last]",
|
|
Value: "0",
|
|
Clock: 1522082244,
|
|
},
|
|
},
|
|
},
|
|
"send one metric with two fields and no extra tags, generates two zabbix metrics": {
|
|
telegrafMetrics: []telegraf.Metric{
|
|
testutil.MustMetric("name",
|
|
map[string]string{
|
|
"host": "hostname",
|
|
},
|
|
map[string]interface{}{
|
|
"valueA": int64(0),
|
|
"valueB": int64(1),
|
|
},
|
|
time.Unix(1522082244, 0),
|
|
),
|
|
},
|
|
zabbixMetrics: []zabbixRequestData{
|
|
{
|
|
Host: "hostname",
|
|
Key: "name.valueA",
|
|
Value: "0",
|
|
Clock: 1522082244,
|
|
},
|
|
{
|
|
Host: "hostname",
|
|
Key: "name.valueB",
|
|
Value: "1",
|
|
Clock: 1522082244,
|
|
},
|
|
},
|
|
},
|
|
"send two metrics with one field and no extra tags, generates two zabbix metrics": {
|
|
telegrafMetrics: []telegraf.Metric{
|
|
testutil.MustMetric("nameA",
|
|
map[string]string{
|
|
"host": "hostname",
|
|
},
|
|
map[string]interface{}{
|
|
"value": int64(0),
|
|
},
|
|
time.Unix(1522082244, 0),
|
|
),
|
|
testutil.MustMetric("nameB",
|
|
map[string]string{
|
|
"host": "hostname",
|
|
},
|
|
map[string]interface{}{
|
|
"value": int64(0),
|
|
},
|
|
time.Unix(1522082244, 0),
|
|
),
|
|
},
|
|
zabbixMetrics: []zabbixRequestData{
|
|
{
|
|
Host: "hostname",
|
|
Key: "nameA.value",
|
|
Value: "0",
|
|
Clock: 1522082244,
|
|
},
|
|
{
|
|
Host: "hostname",
|
|
Key: "nameB.value",
|
|
Value: "0",
|
|
Clock: 1522082244,
|
|
},
|
|
},
|
|
},
|
|
"send two metrics with different hostname, generates two zabbix metrics for different hosts": {
|
|
telegrafMetrics: []telegraf.Metric{
|
|
testutil.MustMetric("name",
|
|
map[string]string{
|
|
"host": "hostnameA",
|
|
},
|
|
map[string]interface{}{
|
|
"value": int64(0),
|
|
},
|
|
time.Unix(1522082244, 0),
|
|
),
|
|
testutil.MustMetric("name",
|
|
map[string]string{
|
|
"host": "hostnameB",
|
|
},
|
|
map[string]interface{}{
|
|
"value": int64(0),
|
|
},
|
|
time.Unix(1522082244, 0),
|
|
),
|
|
},
|
|
zabbixMetrics: []zabbixRequestData{
|
|
{
|
|
Host: "hostnameA",
|
|
Key: "name.value",
|
|
Value: "0",
|
|
Clock: 1522082244,
|
|
},
|
|
{
|
|
Host: "hostnameB",
|
|
Key: "name.value",
|
|
Value: "0",
|
|
Clock: 1522082244,
|
|
},
|
|
},
|
|
},
|
|
"if key_prefix is configured, zabbix metrics should have that prefix in the key": {
|
|
KeyPrefix: "telegraf.",
|
|
telegrafMetrics: []telegraf.Metric{
|
|
testutil.MustMetric("name",
|
|
map[string]string{
|
|
"host": "hostname",
|
|
},
|
|
map[string]interface{}{
|
|
"value": int64(0),
|
|
},
|
|
time.Unix(1522082244, 0),
|
|
),
|
|
},
|
|
zabbixMetrics: []zabbixRequestData{
|
|
{
|
|
Host: "hostname",
|
|
Key: "telegraf.name.value",
|
|
Value: "0",
|
|
Clock: 1522082244,
|
|
},
|
|
},
|
|
},
|
|
"if skip_measurement_prefix is configured, zabbix metrics should have to skip that prefix in the key": {
|
|
SkipMeasurementPrefix: true,
|
|
telegrafMetrics: []telegraf.Metric{
|
|
testutil.MustMetric("name",
|
|
map[string]string{
|
|
"host": "hostname",
|
|
},
|
|
map[string]interface{}{
|
|
"value": int64(0),
|
|
},
|
|
time.Unix(1522082244, 0),
|
|
),
|
|
},
|
|
zabbixMetrics: []zabbixRequestData{
|
|
{
|
|
Host: "hostname",
|
|
Key: "value",
|
|
Value: "0",
|
|
Clock: 1522082244,
|
|
},
|
|
},
|
|
},
|
|
"if AgentActive is configured, zabbix metrics should be sent respecting that protocol": {
|
|
AgentActive: true,
|
|
telegrafMetrics: []telegraf.Metric{
|
|
testutil.MustMetric("name",
|
|
map[string]string{
|
|
"host": "hostname",
|
|
},
|
|
map[string]interface{}{
|
|
"value": int64(0),
|
|
},
|
|
time.Unix(1522082244, 0),
|
|
),
|
|
},
|
|
zabbixMetrics: []zabbixRequestData{
|
|
{
|
|
Host: "hostname",
|
|
Key: "name.value",
|
|
Value: "0",
|
|
Clock: 1522082244,
|
|
},
|
|
},
|
|
},
|
|
"metrics should be time sorted, oldest to newest, to avoid zabbix doing extra work when generating trends": {
|
|
telegrafMetrics: []telegraf.Metric{
|
|
testutil.MustMetric("name",
|
|
map[string]string{
|
|
"host": "hostnameD",
|
|
},
|
|
map[string]interface{}{
|
|
"value": int64(0),
|
|
},
|
|
time.Unix(4444444444, 0),
|
|
),
|
|
testutil.MustMetric("name",
|
|
map[string]string{
|
|
"host": "hostnameC",
|
|
},
|
|
map[string]interface{}{
|
|
"value": int64(0),
|
|
},
|
|
time.Unix(3333333333, 0),
|
|
),
|
|
testutil.MustMetric("name",
|
|
map[string]string{
|
|
"host": "hostnameA",
|
|
},
|
|
map[string]interface{}{
|
|
"value": int64(0),
|
|
},
|
|
time.Unix(1111111111, 0),
|
|
),
|
|
testutil.MustMetric("name",
|
|
map[string]string{
|
|
"host": "hostnameB",
|
|
},
|
|
map[string]interface{}{
|
|
"value": int64(0),
|
|
},
|
|
time.Unix(2222222222, 0),
|
|
),
|
|
},
|
|
zabbixMetrics: []zabbixRequestData{
|
|
{
|
|
Host: "hostnameA",
|
|
Key: "name.value",
|
|
Value: "0",
|
|
Clock: 1111111111,
|
|
},
|
|
{
|
|
Host: "hostnameB",
|
|
Key: "name.value",
|
|
Value: "0",
|
|
Clock: 2222222222,
|
|
},
|
|
{
|
|
Host: "hostnameC",
|
|
Key: "name.value",
|
|
Value: "0",
|
|
Clock: 3333333333,
|
|
},
|
|
{
|
|
Host: "hostnameD",
|
|
Key: "name.value",
|
|
Value: "0",
|
|
Clock: 4444444444,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for desc, test := range tests {
|
|
t.Run(desc, func(t *testing.T) {
|
|
// Simulate a Zabbix server to get the data sent. It has a timeout to avoid waiting forever.
|
|
listener, err := net.Listen("tcp", "127.0.0.1:")
|
|
require.NoError(t, err)
|
|
defer listener.Close()
|
|
|
|
z := &Zabbix{
|
|
Address: listener.Addr().String(),
|
|
KeyPrefix: test.KeyPrefix,
|
|
HostTag: "host",
|
|
SkipMeasurementPrefix: test.SkipMeasurementPrefix,
|
|
AgentActive: test.AgentActive,
|
|
LLDSendInterval: config.Duration(10 * time.Minute),
|
|
Log: testutil.Logger{},
|
|
}
|
|
require.NoError(t, z.Init())
|
|
|
|
wg := sync.WaitGroup{}
|
|
wg.Add(1)
|
|
go func() {
|
|
success := make(chan zabbixRequest, 1)
|
|
|
|
go func() {
|
|
success <- listenForZabbixMetric(t, listener, len(test.zabbixMetrics) == 0)
|
|
}()
|
|
|
|
// By default we use trappers
|
|
requestType := "sender data"
|
|
if test.AgentActive {
|
|
requestType = "agent data"
|
|
}
|
|
|
|
select {
|
|
case request := <-success:
|
|
require.Equal(t, requestType, request.Request)
|
|
compareData(t, test.zabbixMetrics, request.Data)
|
|
case <-time.After(1 * time.Second):
|
|
require.Empty(t, test.zabbixMetrics, "no metrics should be expected if the connection times out")
|
|
}
|
|
|
|
wg.Done()
|
|
}()
|
|
|
|
require.NoError(t, z.Write(test.telegrafMetrics))
|
|
|
|
// Wait for zabbix server emulator to finish
|
|
wg.Wait()
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestLLD tests how LLD metrics are sent simulating the time passing.
|
|
// LLD is sent each LLDSendInterval. Only new data.
|
|
// LLD data is cleared LLDClearInterval.
|
|
func TestLLD(t *testing.T) {
|
|
// Telegraf metric which will be sent repeatedly
|
|
m := testutil.MustMetric(
|
|
"name",
|
|
map[string]string{"host": "hostA", "foo": "bar"},
|
|
map[string]interface{}{"value": int64(0)},
|
|
time.Unix(0, 0),
|
|
)
|
|
|
|
mNew := testutil.MustMetric(
|
|
"name",
|
|
map[string]string{"host": "hostA", "foo": "moo"},
|
|
map[string]interface{}{"value": int64(0)},
|
|
time.Unix(0, 0),
|
|
)
|
|
|
|
// Expected Zabbix metric generated
|
|
zabbixMetric := zabbixRequestData{
|
|
Host: "hostA",
|
|
Key: "telegraf.name.value[bar]",
|
|
Value: "0",
|
|
Clock: 0,
|
|
}
|
|
|
|
// Expected Zabbix metric generated
|
|
zabbixMetricNew := zabbixRequestData{
|
|
Host: "hostA",
|
|
Key: "telegraf.name.value[moo]",
|
|
Value: "0",
|
|
Clock: 0,
|
|
}
|
|
|
|
// Expected Zabbix LLD metric generated
|
|
zabbixLLDMetric := zabbixRequestData{
|
|
Host: "hostA",
|
|
Key: "telegraf.lld.name.foo",
|
|
Value: `{"data":[{"{#FOO}":"bar"}]}`,
|
|
Clock: 0,
|
|
}
|
|
|
|
// Expected Zabbix LLD metric generated
|
|
zabbixLLDMetricNew := zabbixRequestData{
|
|
Host: "hostA",
|
|
Key: "telegraf.lld.name.foo",
|
|
Value: `{"data":[{"{#FOO}":"bar"},{"{#FOO}":"moo"}]}`,
|
|
Clock: 0,
|
|
}
|
|
|
|
// Simulate a Zabbix server to get the data sent
|
|
listener, err := net.Listen("tcp", "127.0.0.1:")
|
|
require.NoError(t, err)
|
|
defer listener.Close()
|
|
|
|
z := &Zabbix{
|
|
Address: listener.Addr().String(),
|
|
KeyPrefix: "telegraf.",
|
|
HostTag: "host",
|
|
LLDSendInterval: config.Duration(10 * time.Minute),
|
|
LLDClearInterval: config.Duration(1 * time.Hour),
|
|
Log: testutil.Logger{},
|
|
}
|
|
require.NoError(t, z.Init())
|
|
|
|
wg := sync.WaitGroup{}
|
|
wg.Add(1)
|
|
|
|
// Read first packet with two metrics, then the first autoregister packet and the second autoregister packet.
|
|
go func() {
|
|
// First packet with metrics
|
|
request := listenForZabbixMetric(t, listener, false)
|
|
compareData(t, []zabbixRequestData{zabbixMetric}, request.Data)
|
|
|
|
// Second packet, while time has not surpassed LLDSendInterval
|
|
request = listenForZabbixMetric(t, listener, false)
|
|
compareData(t, []zabbixRequestData{zabbixMetric}, request.Data)
|
|
|
|
// Third packet, time has surpassed LLDSendInterval, metrics + LLD
|
|
request = listenForZabbixMetric(t, listener, false)
|
|
require.Len(t, request.Data, 2, "Expected 2 metrics")
|
|
request.Data[1].Clock = 0 // Ignore lld request clock
|
|
compareData(t, []zabbixRequestData{zabbixMetric, zabbixLLDMetric}, request.Data)
|
|
|
|
// Fourth packet with metrics
|
|
request = listenForZabbixMetric(t, listener, false)
|
|
compareData(t, []zabbixRequestData{zabbixMetric}, request.Data)
|
|
|
|
// Fifth packet, time has surpassed LLDSendInterval, metrics. No LLD as there is nothing new.
|
|
request = listenForZabbixMetric(t, listener, false)
|
|
compareData(t, []zabbixRequestData{zabbixMetric}, request.Data)
|
|
|
|
// Sixth packet, new LLD info, but time has not surpassed LLDSendInterval
|
|
request = listenForZabbixMetric(t, listener, false)
|
|
compareData(t, []zabbixRequestData{zabbixMetricNew}, request.Data)
|
|
|
|
// Seventh packet, time has surpassed LLDSendInterval, metrics + LLD.
|
|
// Also, time has surpassed LLDClearInterval, so LLD is cleared.
|
|
request = listenForZabbixMetric(t, listener, false)
|
|
require.Len(t, request.Data, 2, "Expected 2 metrics")
|
|
request.Data[1].Clock = 0 // Ignore lld request clock
|
|
compareData(t, []zabbixRequestData{zabbixMetric, zabbixLLDMetricNew}, request.Data)
|
|
|
|
// Eighth packet, time host not surpassed LLDSendInterval, just metrics.
|
|
request = listenForZabbixMetric(t, listener, false)
|
|
compareData(t, []zabbixRequestData{zabbixMetric}, request.Data)
|
|
|
|
// Ninth packet, time has surpassed LLDSendInterval, metrics + LLD.
|
|
// Just the info of the zabbixMetric as zabbixMetricNew has not been seen since LLDClearInterval.
|
|
request = listenForZabbixMetric(t, listener, false)
|
|
require.Len(t, request.Data, 2, "Expected 2 metrics")
|
|
request.Data[1].Clock = 0 // Ignore lld request clock
|
|
compareData(t, []zabbixRequestData{zabbixMetric, zabbixLLDMetric}, request.Data)
|
|
|
|
wg.Done()
|
|
}()
|
|
|
|
// First packet
|
|
require.NoError(t, z.Write([]telegraf.Metric{m}))
|
|
|
|
// Second packet, while time has not surpassed LLDSendInterval
|
|
require.NoError(t, z.Write([]telegraf.Metric{m}))
|
|
|
|
// Simulate time passing for a new LLD send
|
|
z.lldLastSend = time.Now().Add(-time.Duration(z.LLDSendInterval)).Add(-time.Millisecond)
|
|
|
|
// Third packet, time has surpassed LLDSendInterval, metrics + LLD
|
|
require.NoError(t, z.Write([]telegraf.Metric{m}))
|
|
|
|
// Fourth packet
|
|
require.NoError(t, z.Write([]telegraf.Metric{m}))
|
|
|
|
// Simulate time passing for a new LLD send
|
|
z.lldLastSend = time.Now().Add(-time.Duration(z.LLDSendInterval)).Add(-time.Millisecond)
|
|
|
|
// Fifth packet, time has surpassed LLDSendInterval, metrics. No LLD as there is nothing new.
|
|
require.NoError(t, z.Write([]telegraf.Metric{m}))
|
|
|
|
// Sixth packet, new LLD info, but time has not surpassed LLDSendInterval
|
|
require.NoError(t, z.Write([]telegraf.Metric{mNew}))
|
|
|
|
// Simulate time passing for LLD clear
|
|
z.lldLastSend = time.Now().Add(-time.Duration(z.LLDClearInterval)).Add(-time.Millisecond)
|
|
|
|
// Seventh packet, time has surpassed LLDSendInterval and LLDClearInterval, metrics + LLD.
|
|
// LLD will be cleared.
|
|
require.NoError(t, z.Write([]telegraf.Metric{m}))
|
|
|
|
// Eighth packet, time host not surpassed LLDSendInterval, just metrics.
|
|
require.NoError(t, z.Write([]telegraf.Metric{m}))
|
|
|
|
// Simulate time passing for a new LLD send
|
|
z.lldLastSend = time.Now().Add(-time.Duration(z.LLDSendInterval)).Add(-time.Millisecond)
|
|
|
|
// Ninth packet, time has surpassed LLDSendInterval, metrics + LLD.
|
|
require.NoError(t, z.Write([]telegraf.Metric{m}))
|
|
|
|
// Wait for zabbix server emulator to finish
|
|
wg.Wait()
|
|
}
|
|
|
|
// TestAutoregister tests that autoregistration requests are sent to zabbix if enabled
|
|
func TestAutoregister(t *testing.T) {
|
|
// Simulate a Zabbix server to get the data sent
|
|
listener, err := net.Listen("tcp", "127.0.0.1:")
|
|
require.NoError(t, err)
|
|
defer listener.Close()
|
|
|
|
z := &Zabbix{
|
|
Address: listener.Addr().String(),
|
|
KeyPrefix: "telegraf.",
|
|
HostTag: "host",
|
|
SkipMeasurementPrefix: false,
|
|
AgentActive: false,
|
|
Autoregister: "xxx",
|
|
AutoregisterResendInterval: config.Duration(time.Minute * 5),
|
|
Log: testutil.Logger{},
|
|
}
|
|
require.NoError(t, z.Init())
|
|
|
|
wg := sync.WaitGroup{}
|
|
wg.Add(1)
|
|
|
|
// Read first packet with two metrics, then the first autoregister packet and the second autoregister packet.
|
|
go func() {
|
|
// Accept packet with the two metrics sent
|
|
_ = listenForZabbixMetric(t, listener, false)
|
|
|
|
// Read the first autoregister packet
|
|
request := listenForZabbixMetric(t, listener, false)
|
|
require.Equal(t, "active checks", request.Request)
|
|
require.Equal(t, "xxx", request.HostMetadata)
|
|
|
|
hostsRegistered := []string{request.Host}
|
|
|
|
// Read the second autoregister packet
|
|
request = listenForZabbixMetric(t, listener, false)
|
|
require.Equal(t, "active checks", request.Request)
|
|
require.Equal(t, "xxx", request.HostMetadata)
|
|
|
|
// Check we have received autoregistration for both hosts
|
|
hostsRegistered = append(hostsRegistered, request.Host)
|
|
require.ElementsMatch(t, []string{"hostA", "hostB"}, hostsRegistered)
|
|
|
|
wg.Done()
|
|
}()
|
|
|
|
err = z.Write([]telegraf.Metric{
|
|
testutil.MustMetric(
|
|
"name",
|
|
map[string]string{"host": "hostA"},
|
|
map[string]interface{}{"value": int64(0)},
|
|
time.Now(),
|
|
),
|
|
testutil.MustMetric(
|
|
"name",
|
|
map[string]string{"host": "hostB"},
|
|
map[string]interface{}{"value": int64(0)},
|
|
time.Now(),
|
|
),
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// Wait for zabbix server emulator to finish
|
|
wg.Wait()
|
|
}
|
|
|
|
// compareData compares generated data with expected data ignoring slice order if all Clocks are
|
|
// the same.
|
|
// This is useful for metrics with several fields that should produce several Zabbix values that
|
|
// could not be sorted by clock
|
|
func compareData(t *testing.T, expected []zabbixRequestData, data []zabbixRequestData) {
|
|
t.Helper()
|
|
|
|
var clock int64
|
|
|
|
sameClock := true
|
|
|
|
// Check if all clocks are the same
|
|
for i := 0; i < len(data); i++ {
|
|
if i == 0 {
|
|
clock = data[i].Clock
|
|
} else if clock != data[i].Clock {
|
|
sameClock = false
|
|
|
|
break
|
|
}
|
|
}
|
|
|
|
// Zabbix requests with LLD data contains a JSON value with an array of dictionaries.
|
|
// That array order depends in the access to a map, so it does not have a defined order.
|
|
// To compare the data, we need to sort the array of dictionaries.
|
|
// Before comparing the requests, sort those values.
|
|
// To detect if a request contains LLD data, try to unmarshal it to a ZabbixLLDValue.
|
|
// If it could be unmarshalled, sort the slice and marshal it again.
|
|
for i := 0; i < len(data); i++ {
|
|
var lldValue zabbixLLDValue
|
|
|
|
err := json.Unmarshal([]byte(data[i].Value), &lldValue)
|
|
if err == nil {
|
|
sort.Slice(lldValue.Data, func(i, j int) bool {
|
|
// Generate a global order based on the keys and values present in the map
|
|
keysValuesI := make([]string, 0, len(lldValue.Data[i])*2)
|
|
keysValuesJ := make([]string, 0, len(lldValue.Data[j])*2)
|
|
for k, v := range lldValue.Data[i] {
|
|
keysValuesI = append(keysValuesI, k, v)
|
|
}
|
|
for k, v := range lldValue.Data[j] {
|
|
keysValuesJ = append(keysValuesJ, k, v)
|
|
}
|
|
|
|
sort.Strings(keysValuesI)
|
|
sort.Strings(keysValuesJ)
|
|
|
|
return strings.Join(keysValuesI, "") < strings.Join(keysValuesJ, "")
|
|
})
|
|
sortedValue, err := json.Marshal(lldValue)
|
|
require.NoError(t, err)
|
|
|
|
data[i].Value = string(sortedValue)
|
|
}
|
|
}
|
|
|
|
if sameClock {
|
|
require.ElementsMatch(t, expected, data)
|
|
} else {
|
|
require.Equal(t, expected, data)
|
|
}
|
|
}
|
|
|
|
// listenForZabbixMetric starts a TCP server listening for one Zabbix metric.
|
|
// ignoreAcceptError is used to ignore the error when the server is closed.
|
|
func listenForZabbixMetric(t *testing.T, listener net.Listener, ignoreAcceptError bool) zabbixRequest {
|
|
t.Helper()
|
|
|
|
conn, err := listener.Accept()
|
|
if err != nil && ignoreAcceptError {
|
|
return zabbixRequest{}
|
|
}
|
|
|
|
require.NoError(t, err)
|
|
|
|
// Obtain request from the mock zabbix server
|
|
// Read protocol header and version
|
|
header := make([]byte, 5)
|
|
_, err = conn.Read(header)
|
|
require.NoError(t, err)
|
|
|
|
// Read data length
|
|
dataLengthRaw := make([]byte, 8)
|
|
_, err = conn.Read(dataLengthRaw)
|
|
require.NoError(t, err)
|
|
|
|
dataLength := binary.LittleEndian.Uint64(dataLengthRaw)
|
|
|
|
// Read data content
|
|
content := make([]byte, dataLength)
|
|
_, err = conn.Read(content)
|
|
require.NoError(t, err)
|
|
|
|
// The zabbix output checks that there are not errors
|
|
// Simulated response from the server
|
|
resp := []byte("ZBXD\x01\x00\x00\x00\x00\x00\x00\x00\x00{\"response\": \"success\", \"info\": \"\"}\n")
|
|
_, err = conn.Write(resp)
|
|
require.NoError(t, err)
|
|
|
|
// Close connection after reading the client data
|
|
conn.Close()
|
|
|
|
// Strip zabbix header and get JSON request
|
|
var request zabbixRequest
|
|
require.NoError(t, json.Unmarshal(content, &request))
|
|
|
|
return request
|
|
}
|
|
|
|
func TestBuildZabbixMetric(t *testing.T) {
|
|
keyPrefix := "prefix."
|
|
hostTag := "host"
|
|
|
|
z := &Zabbix{
|
|
KeyPrefix: keyPrefix,
|
|
HostTag: hostTag,
|
|
}
|
|
|
|
zm, err := z.buildZabbixMetric(testutil.MustMetric(
|
|
"name",
|
|
map[string]string{hostTag: "hostA", "foo": "bar", "a": "b"},
|
|
map[string]interface{}{},
|
|
time.Now()),
|
|
"value",
|
|
1,
|
|
)
|
|
require.NoError(t, err)
|
|
require.Equal(t, fmt.Sprintf("%sname.value[b,bar]", keyPrefix), zm.Key)
|
|
|
|
zm, err = z.buildZabbixMetric(testutil.MustMetric(
|
|
"name",
|
|
map[string]string{hostTag: "hostA"},
|
|
map[string]interface{}{},
|
|
time.Now()),
|
|
"value",
|
|
1,
|
|
)
|
|
require.NoError(t, err)
|
|
require.Equal(t, fmt.Sprintf("%sname.value", keyPrefix), zm.Key)
|
|
}
|
|
|
|
func TestGetHostname(t *testing.T) {
|
|
hostname, err := os.Hostname()
|
|
require.NoError(t, err)
|
|
|
|
tests := map[string]struct {
|
|
HostTag string
|
|
Host string
|
|
Tags map[string]string
|
|
Result string
|
|
}{
|
|
"metric with host tag": {
|
|
HostTag: "host",
|
|
Tags: map[string]string{
|
|
"host": "bar",
|
|
},
|
|
Result: "bar",
|
|
},
|
|
"metric with host tag changed": {
|
|
HostTag: "source",
|
|
Tags: map[string]string{
|
|
"source": "bar",
|
|
},
|
|
Result: "bar",
|
|
},
|
|
"metric with no host tag": {
|
|
Tags: map[string]string{},
|
|
Result: hostname,
|
|
},
|
|
}
|
|
|
|
for desc, test := range tests {
|
|
t.Run(desc, func(t *testing.T) {
|
|
metric := testutil.MustMetric(
|
|
"name",
|
|
test.Tags,
|
|
map[string]interface{}{},
|
|
time.Now(),
|
|
)
|
|
|
|
host, err := getHostname(test.HostTag, metric)
|
|
require.NoError(t, err)
|
|
require.Equal(t, test.Result, host)
|
|
})
|
|
}
|
|
}
|