358 lines
12 KiB
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))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|