influxdb/collectd/collectd_test.go

358 lines
12 KiB
Go

package collectd_test
import (
"encoding/hex"
"fmt"
"net"
"testing"
"time"
"github.com/influxdb/influxdb"
"github.com/influxdb/influxdb/collectd"
"github.com/kimor79/gollectd"
)
type testServer string
type serverResponses []serverResponse
type serverResponse struct {
database string
retentionPolicy string
points []influxdb.Point
}
var responses = make(chan *serverResponse, 1024)
func (testServer) WriteSeries(database, retentionPolicy string, points []influxdb.Point) (uint64, error) {
responses <- &serverResponse{
database: database,
retentionPolicy: retentionPolicy,
points: points,
}
return 0, nil
}
func (testServer) ResponseN(n int) ([]*serverResponse, error) {
var a []*serverResponse
for {
select {
case r := <-responses:
a = append(a, r)
if len(a) == n {
return a, nil
}
case <-time.After(time.Second):
return a, fmt.Errorf("unexpected response count: expected: %d, actual: %d", n, len(a))
}
}
}
func TestServer_ListenAndServe_ErrBindAddressRequired(t *testing.T) {
var (
ts testServer
s = collectd.NewServer(ts, "foo")
)
e := collectd.ListenAndServe(s, "")
if e == nil {
t.Fatalf("expected an error, got %v", e)
}
}
func TestServer_ListenAndServe_ErrDatabaseNotSpecified(t *testing.T) {
var (
ts testServer
s = collectd.NewServer(ts, "foo")
)
e := collectd.ListenAndServe(s, "127.0.0.1:25826")
if e == nil {
t.Fatalf("expected an error, got %v", e)
}
}
func TestServer_ListenAndServe_ErrCouldNotParseTypesDBFile(t *testing.T) {
var (
ts testServer
s = collectd.NewServer(ts, "foo")
)
s.Database = "foo"
e := collectd.ListenAndServe(s, "127.0.0.1:25829")
if e == nil {
t.Fatalf("expected an error, got %v", e)
}
}
func TestServer_ListenAndServe_Success(t *testing.T) {
var (
ts testServer
// You can typically find this on your mac here: "/usr/local/Cellar/collectd/5.4.1/share/collectd/types.db"
s = collectd.NewServer(ts, "./collectd_test.conf")
)
s.Database = "counter"
e := collectd.ListenAndServe(s, "127.0.0.1:25830")
defer s.Close()
if e != nil {
t.Fatalf("err does not match. expected %v, got %v", nil, e)
}
}
func TestServer_Close_ErrServerClosed(t *testing.T) {
var (
ts testServer
// You can typically find this on your mac here: "/usr/local/Cellar/collectd/5.4.1/share/collectd/types.db"
s = collectd.NewServer(ts, "./collectd_test.conf")
)
s.Database = "counter"
e := collectd.ListenAndServe(s, "127.0.0.1:25830")
if e != nil {
t.Fatalf("err does not match. expected %v, got %v", nil, e)
}
s.Close()
e = s.Close()
if e == nil {
t.Fatalf("expected an error, got %v", e)
}
}
func TestServer_ListenAndServe_ErrResolveUDPAddr(t *testing.T) {
var (
ts testServer
s = collectd.NewServer(ts, "./collectd_test.conf")
)
s.Database = "counter"
e := collectd.ListenAndServe(s, "foo")
if e == nil {
t.Fatalf("expected an error, got %v", e)
}
}
func TestServer_ListenAndServe_ErrListenUDP(t *testing.T) {
var (
ts testServer
s = collectd.NewServer(ts, "./collectd_test.conf")
)
//Open a udp listener on the port prior to force it to err
addr, _ := net.ResolveUDPAddr("udp", "127.0.0.1:25826")
conn, _ := net.ListenUDP("udp", addr)
defer conn.Close()
s.Database = "counter"
e := collectd.ListenAndServe(s, "127.0.0.1:25826")
if e == nil {
t.Fatalf("expected an error, got %v", e)
}
}
func TestServer_Serve_Success(t *testing.T) {
// clear any previous responses
var (
ts testServer
// You can typically find this on your mac here: "/usr/local/Cellar/collectd/5.4.1/share/collectd/types.db"
s = collectd.NewServer(ts, "./collectd_test.conf")
addr = "127.0.0.1:25830"
)
s.Database = "counter"
e := collectd.ListenAndServe(s, addr)
defer s.Close()
if e != nil {
t.Fatalf("err does not match. expected %v, got %v", nil, e)
}
conn, e := net.Dial("udp", addr)
defer conn.Close()
if e != nil {
t.Fatalf("err does not match. expected %v, got %v", nil, e)
}
buf, e := hex.DecodeString("0000000e6c6f63616c686f7374000008000c1512b2e40f5da16f0009000c00000002800000000002000e70726f636573736573000004000d70735f7374617465000005000c72756e6e696e67000006000f000101000000000000f03f0008000c1512b2e40f5db90f0005000d736c656570696e67000006000f0001010000000000c06f400008000c1512b2e40f5dc4a40005000c7a6f6d62696573000006000f00010100000000000000000008000c1512b2e40f5de10b0005000c73746f70706564000006000f00010100000000000000000008000c1512b2e40f5deac20005000b706167696e67000006000f00010100000000000000000008000c1512b2e40f5df59b0005000c626c6f636b6564000006000f00010100000000000000000008000c1512b2e40f7ee0610004000e666f726b5f726174650000050005000006000f000102000000000004572f0008000c1512b2e68e0635e6000200086370750000030006300000040008637075000005000975736572000006000f0001020000000000204f9c0008000c1512b2e68e0665d6000500096e696365000006000f000102000000000000caa30008000c1512b2e68e06789c0005000b73797374656d000006000f00010200000000000607050008000c1512b2e68e06818e0005000969646c65000006000f0001020000000003b090ae0008000c1512b2e68e068bcf0005000977616974000006000f000102000000000000f6810008000c1512b2e68e069c7d0005000e696e74657272757074000006000f000102000000000000001d0008000c1512b2e68e069fec0005000c736f6674697271000006000f0001020000000000000a2a0008000c1512b2e68e06a2b20005000a737465616c000006000f00010200000000000000000008000c1512b2e68e0708d60003000631000005000975736572000006000f00010200000000001d48c60008000c1512b2e68e070c16000500096e696365000006000f0001020000000000007fe60008000c1512b2e68e0710790005000b73797374656d000006000f00010200000000000667890008000c1512b2e68e0713bb0005000969646c65000006000f00010200000000025d0e470008000c1512b2e68e0717790005000977616974000006000f000102000000000002500e0008000c1512b2e68e071bc00005000e696e74657272757074000006000f00010200000000000000000008000c1512b2e68e071f800005000c736f6674697271000006000f00010200000000000006050008000c1512b2e68e07221e0005000a737465616c000006000f00010200000000000000000008000c1512b2e68e0726eb0003000632000005000975736572000006000f00010200000000001ff3e40008000c1512b2e68e0728cb000500096e696365000006000f000102000000000000ca210008000c1512b2e68e072ae70005000b73797374656d000006000f000102000000000006eabe0008000c1512b2e68e072f2f0005000977616974000006000f000102000000000000c1300008000c1512b2e68e072ccb0005000969646c65000006000f00010200000000025b5abb0008000c1512b2e68e07312c0005000e696e74657272757074000006000f00010200000000000000070008000c1512b2e68e0733520005000c736f6674697271000006000f00010200000000000007260008000c1512b2e68e0735b60005000a737465616c000006000f00010200000000000000000008000c1512b2e68e07828d0003000633000005000975736572000006000f000102000000000020f50a0008000c1512b2e68e0787ac000500096e696365000006000f0001020000000000008368")
if e != nil {
t.Fatalf("err from hex.DecodeString does not match. expected %v, got %v", nil, e)
}
_, e = conn.Write(buf)
if e != nil {
t.Fatalf("err does not match. expected %v, got %v", nil, e)
}
if _, err := ts.ResponseN(33); err != nil {
t.Fatal(err)
}
}
func TestUnmarshal_Points(t *testing.T) {
/*
This is a sample of what data can be represented like in json
[
{
"values": [197141504, 175136768],
"dstypes": ["counter", "counter"],
"dsnames": ["read", "write"],
"time": 1251533299,
"interval": 10,
"host": "leeloo.lan.home.verplant.org",
"plugin": "disk",
"plugin_instance": "sda",
"type": "disk_octets",
"type_instance": ""
},
]
*/
var tests = []struct {
name string
packet gollectd.Packet
points []influxdb.Point
}{
{
name: "single value",
points: []influxdb.Point{
{Name: "disk_read", Fields: map[string]interface{}{"disk_read": float64(1)}},
},
packet: gollectd.Packet{
Plugin: "disk",
Values: []gollectd.Value{
{Name: "read", Value: 1},
},
},
},
{
name: "multi value",
points: []influxdb.Point{
{Name: "disk_read", Fields: map[string]interface{}{"disk_read": float64(1)}},
{Name: "disk_write", Fields: map[string]interface{}{"disk_write": float64(5)}},
},
packet: gollectd.Packet{
Plugin: "disk",
Values: []gollectd.Value{
{Name: "read", Value: 1},
{Name: "write", Value: 5},
},
},
},
{
name: "tags",
points: []influxdb.Point{
{
Name: "disk_read",
Tags: map[string]string{"host": "server01", "instance": "sdk", "type": "disk_octets", "type_instance": "single"},
Fields: map[string]interface{}{"disk_read": float64(1)},
},
},
packet: gollectd.Packet{
Plugin: "disk",
Hostname: "server01",
PluginInstance: "sdk",
Type: "disk_octets",
TypeInstance: "single",
Values: []gollectd.Value{
{Name: "read", Value: 1},
},
},
},
}
for _, test := range tests {
t.Logf("testing %q", test.name)
points := collectd.Unmarshal(&test.packet)
if len(points) != len(test.points) {
t.Errorf("points len mismatch. expected %d, got %d", len(test.points), len(points))
}
for i, m := range test.points {
// test name
name := fmt.Sprintf("%s_%s", test.packet.Plugin, test.packet.Values[i].Name)
if m.Name != name {
t.Errorf("point name mismatch. expected %q, got %q", name, m.Name)
}
// test value
mv := m.Fields[m.Name].(float64)
pv := test.packet.Values[i].Value
if mv != pv {
t.Errorf("point value mismatch. expected %v, got %v", pv, mv)
}
// test tags
if test.packet.Hostname != m.Tags["host"] {
t.Errorf(`point tags["host"] mismatch. expected %q, got %q`, test.packet.Hostname, m.Tags["host"])
}
if test.packet.PluginInstance != m.Tags["instance"] {
t.Errorf(`point tags["instance"] mismatch. expected %q, got %q`, test.packet.PluginInstance, m.Tags["instance"])
}
if test.packet.Type != m.Tags["type"] {
t.Errorf(`point tags["type"] mismatch. expected %q, got %q`, test.packet.Type, m.Tags["type"])
}
if test.packet.TypeInstance != m.Tags["type_instance"] {
t.Errorf(`point tags["type_instance"] mismatch. expected %q, got %q`, test.packet.TypeInstance, m.Tags["type_instance"])
}
}
}
}
func TestUnmarshal_Time(t *testing.T) {
// Its important to remember that collectd stores high resolution time
// as "near" nanoseconds (2^30) so we have to take that into account
// when feeding time into the test.
// Since we only store microseconds, we round it off (mostly to make testing easier)
testTime := time.Now().UTC().Round(time.Microsecond)
var timeHR = func(tm time.Time) uint64 {
sec, nsec := tm.Unix(), tm.UnixNano()%1000000000
hr := (sec << 30) + (nsec * 1000000000 / 1073741824)
return uint64(hr)
}
var tests = []struct {
name string
packet gollectd.Packet
points []influxdb.Point
}{
{
name: "Should parse timeHR properly",
packet: gollectd.Packet{
TimeHR: timeHR(testTime),
Values: []gollectd.Value{
{
Value: 1,
},
},
},
points: []influxdb.Point{
{Timestamp: testTime},
},
},
{
name: "Should parse time properly",
packet: gollectd.Packet{
Time: uint64(testTime.Round(time.Second).Unix()),
Values: []gollectd.Value{
{
Value: 1,
},
},
},
points: []influxdb.Point{
{Timestamp: testTime.Round(time.Second)},
},
},
}
for _, test := range tests {
t.Logf("testing %q", test.name)
points := collectd.Unmarshal(&test.packet)
if len(points) != len(test.points) {
t.Errorf("point len mismatch. expected %d, got %d", len(test.points), len(points))
}
for _, p := range points {
if test.packet.TimeHR > 0 {
if p.Timestamp.Format(time.RFC3339Nano) != testTime.Format(time.RFC3339Nano) {
t.Errorf("timestamp mis-match, got %v, expected %v", p.Timestamp.Format(time.RFC3339Nano), testTime.Format(time.RFC3339Nano))
} else if p.Timestamp.Format(time.RFC3339) != testTime.Format(time.RFC3339) {
t.Errorf("timestamp mis-match, got %v, expected %v", p.Timestamp.Format(time.RFC3339), testTime.Format(time.RFC3339))
}
}
}
}
}