1120 lines
30 KiB
Go
1120 lines
30 KiB
Go
package docker
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"io/ioutil"
|
|
"sort"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/docker/docker/api/types"
|
|
"github.com/docker/docker/api/types/swarm"
|
|
"github.com/influxdata/telegraf"
|
|
"github.com/influxdata/telegraf/testutil"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
type MockClient struct {
|
|
InfoF func(ctx context.Context) (types.Info, error)
|
|
ContainerListF func(ctx context.Context, options types.ContainerListOptions) ([]types.Container, error)
|
|
ContainerStatsF func(ctx context.Context, containerID string, stream bool) (types.ContainerStats, error)
|
|
ContainerInspectF func(ctx context.Context, containerID string) (types.ContainerJSON, error)
|
|
ServiceListF func(ctx context.Context, options types.ServiceListOptions) ([]swarm.Service, error)
|
|
TaskListF func(ctx context.Context, options types.TaskListOptions) ([]swarm.Task, error)
|
|
NodeListF func(ctx context.Context, options types.NodeListOptions) ([]swarm.Node, error)
|
|
}
|
|
|
|
func (c *MockClient) Info(ctx context.Context) (types.Info, error) {
|
|
return c.InfoF(ctx)
|
|
}
|
|
|
|
func (c *MockClient) ContainerList(
|
|
ctx context.Context,
|
|
options types.ContainerListOptions,
|
|
) ([]types.Container, error) {
|
|
return c.ContainerListF(ctx, options)
|
|
}
|
|
|
|
func (c *MockClient) ContainerStats(
|
|
ctx context.Context,
|
|
containerID string,
|
|
stream bool,
|
|
) (types.ContainerStats, error) {
|
|
return c.ContainerStatsF(ctx, containerID, stream)
|
|
}
|
|
|
|
func (c *MockClient) ContainerInspect(
|
|
ctx context.Context,
|
|
containerID string,
|
|
) (types.ContainerJSON, error) {
|
|
return c.ContainerInspectF(ctx, containerID)
|
|
}
|
|
|
|
func (c *MockClient) ServiceList(
|
|
ctx context.Context,
|
|
options types.ServiceListOptions,
|
|
) ([]swarm.Service, error) {
|
|
return c.ServiceListF(ctx, options)
|
|
}
|
|
|
|
func (c *MockClient) TaskList(
|
|
ctx context.Context,
|
|
options types.TaskListOptions,
|
|
) ([]swarm.Task, error) {
|
|
return c.TaskListF(ctx, options)
|
|
}
|
|
|
|
func (c *MockClient) NodeList(
|
|
ctx context.Context,
|
|
options types.NodeListOptions,
|
|
) ([]swarm.Node, error) {
|
|
return c.NodeListF(ctx, options)
|
|
}
|
|
|
|
var baseClient = MockClient{
|
|
InfoF: func(context.Context) (types.Info, error) {
|
|
return info, nil
|
|
},
|
|
ContainerListF: func(context.Context, types.ContainerListOptions) ([]types.Container, error) {
|
|
return containerList, nil
|
|
},
|
|
ContainerStatsF: func(c context.Context, s string, b bool) (types.ContainerStats, error) {
|
|
return containerStats(s), nil
|
|
},
|
|
ContainerInspectF: func(context.Context, string) (types.ContainerJSON, error) {
|
|
return containerInspect(), nil
|
|
},
|
|
ServiceListF: func(context.Context, types.ServiceListOptions) ([]swarm.Service, error) {
|
|
return ServiceList, nil
|
|
},
|
|
TaskListF: func(context.Context, types.TaskListOptions) ([]swarm.Task, error) {
|
|
return TaskList, nil
|
|
},
|
|
NodeListF: func(context.Context, types.NodeListOptions) ([]swarm.Node, error) {
|
|
return NodeList, nil
|
|
},
|
|
}
|
|
|
|
func newClient(host string, tlsConfig *tls.Config) (Client, error) {
|
|
return &baseClient, nil
|
|
}
|
|
|
|
func TestDockerGatherContainerStats(t *testing.T) {
|
|
var acc testutil.Accumulator
|
|
stats := testStats()
|
|
|
|
tags := map[string]string{
|
|
"container_name": "redis",
|
|
"container_image": "redis/image",
|
|
}
|
|
|
|
parseContainerStats(stats, &acc, tags, "123456789", true, true, "linux")
|
|
|
|
// test docker_container_net measurement
|
|
netfields := map[string]interface{}{
|
|
"rx_dropped": uint64(1),
|
|
"rx_bytes": uint64(2),
|
|
"rx_errors": uint64(3),
|
|
"tx_packets": uint64(4),
|
|
"tx_dropped": uint64(1),
|
|
"rx_packets": uint64(2),
|
|
"tx_errors": uint64(3),
|
|
"tx_bytes": uint64(4),
|
|
"container_id": "123456789",
|
|
}
|
|
nettags := copyTags(tags)
|
|
nettags["network"] = "eth0"
|
|
acc.AssertContainsTaggedFields(t, "docker_container_net", netfields, nettags)
|
|
|
|
netfields = map[string]interface{}{
|
|
"rx_dropped": uint64(6),
|
|
"rx_bytes": uint64(8),
|
|
"rx_errors": uint64(10),
|
|
"tx_packets": uint64(12),
|
|
"tx_dropped": uint64(6),
|
|
"rx_packets": uint64(8),
|
|
"tx_errors": uint64(10),
|
|
"tx_bytes": uint64(12),
|
|
"container_id": "123456789",
|
|
}
|
|
nettags = copyTags(tags)
|
|
nettags["network"] = "total"
|
|
acc.AssertContainsTaggedFields(t, "docker_container_net", netfields, nettags)
|
|
|
|
// test docker_blkio measurement
|
|
blkiotags := copyTags(tags)
|
|
blkiotags["device"] = "6:0"
|
|
blkiofields := map[string]interface{}{
|
|
"io_service_bytes_recursive_read": uint64(100),
|
|
"io_serviced_recursive_write": uint64(101),
|
|
"container_id": "123456789",
|
|
}
|
|
acc.AssertContainsTaggedFields(t, "docker_container_blkio", blkiofields, blkiotags)
|
|
|
|
blkiotags = copyTags(tags)
|
|
blkiotags["device"] = "total"
|
|
blkiofields = map[string]interface{}{
|
|
"io_service_bytes_recursive_read": uint64(100),
|
|
"io_serviced_recursive_write": uint64(302),
|
|
"container_id": "123456789",
|
|
}
|
|
acc.AssertContainsTaggedFields(t, "docker_container_blkio", blkiofields, blkiotags)
|
|
|
|
// test docker_container_mem measurement
|
|
memfields := map[string]interface{}{
|
|
"active_anon": uint64(0),
|
|
"active_file": uint64(1),
|
|
"cache": uint64(0),
|
|
"container_id": "123456789",
|
|
"fail_count": uint64(1),
|
|
"hierarchical_memory_limit": uint64(0),
|
|
"inactive_anon": uint64(0),
|
|
"inactive_file": uint64(3),
|
|
"limit": uint64(2000),
|
|
"mapped_file": uint64(0),
|
|
"max_usage": uint64(1001),
|
|
"pgfault": uint64(2),
|
|
"pgmajfault": uint64(0),
|
|
"pgpgin": uint64(0),
|
|
"pgpgout": uint64(0),
|
|
"rss_huge": uint64(0),
|
|
"rss": uint64(0),
|
|
"total_active_anon": uint64(0),
|
|
"total_active_file": uint64(0),
|
|
"total_cache": uint64(0),
|
|
"total_inactive_anon": uint64(0),
|
|
"total_inactive_file": uint64(0),
|
|
"total_mapped_file": uint64(0),
|
|
"total_pgfault": uint64(0),
|
|
"total_pgmajfault": uint64(0),
|
|
"total_pgpgin": uint64(4),
|
|
"total_pgpgout": uint64(0),
|
|
"total_rss_huge": uint64(444),
|
|
"total_rss": uint64(44),
|
|
"total_unevictable": uint64(0),
|
|
"total_writeback": uint64(55),
|
|
"unevictable": uint64(0),
|
|
"usage_percent": float64(55.55),
|
|
"usage": uint64(1111),
|
|
"writeback": uint64(0),
|
|
}
|
|
|
|
acc.AssertContainsTaggedFields(t, "docker_container_mem", memfields, tags)
|
|
|
|
// test docker_container_cpu measurement
|
|
cputags := copyTags(tags)
|
|
cputags["cpu"] = "cpu-total"
|
|
cpufields := map[string]interface{}{
|
|
"usage_total": uint64(500),
|
|
"usage_in_usermode": uint64(100),
|
|
"usage_in_kernelmode": uint64(200),
|
|
"usage_system": uint64(100),
|
|
"throttling_periods": uint64(1),
|
|
"throttling_throttled_periods": uint64(0),
|
|
"throttling_throttled_time": uint64(0),
|
|
"usage_percent": float64(400.0),
|
|
"container_id": "123456789",
|
|
}
|
|
acc.AssertContainsTaggedFields(t, "docker_container_cpu", cpufields, cputags)
|
|
|
|
cputags["cpu"] = "cpu0"
|
|
cpu0fields := map[string]interface{}{
|
|
"usage_total": uint64(1),
|
|
"container_id": "123456789",
|
|
}
|
|
acc.AssertContainsTaggedFields(t, "docker_container_cpu", cpu0fields, cputags)
|
|
|
|
cputags["cpu"] = "cpu1"
|
|
cpu1fields := map[string]interface{}{
|
|
"usage_total": uint64(1002),
|
|
"container_id": "123456789",
|
|
}
|
|
acc.AssertContainsTaggedFields(t, "docker_container_cpu", cpu1fields, cputags)
|
|
|
|
// Those tagged filed should not be present because of offline CPUs
|
|
cputags["cpu"] = "cpu2"
|
|
cpu2fields := map[string]interface{}{
|
|
"usage_total": uint64(0),
|
|
"container_id": "123456789",
|
|
}
|
|
acc.AssertDoesNotContainsTaggedFields(t, "docker_container_cpu", cpu2fields, cputags)
|
|
|
|
cputags["cpu"] = "cpu3"
|
|
cpu3fields := map[string]interface{}{
|
|
"usage_total": uint64(0),
|
|
"container_id": "123456789",
|
|
}
|
|
acc.AssertDoesNotContainsTaggedFields(t, "docker_container_cpu", cpu3fields, cputags)
|
|
}
|
|
|
|
func TestDocker_WindowsMemoryContainerStats(t *testing.T) {
|
|
var acc testutil.Accumulator
|
|
|
|
d := Docker{
|
|
Log: testutil.Logger{},
|
|
newClient: func(string, *tls.Config) (Client, error) {
|
|
return &MockClient{
|
|
InfoF: func(ctx context.Context) (types.Info, error) {
|
|
return info, nil
|
|
},
|
|
ContainerListF: func(ctx context.Context, options types.ContainerListOptions) ([]types.Container, error) {
|
|
return containerList, nil
|
|
},
|
|
ContainerStatsF: func(ctx context.Context, containerID string, stream bool) (types.ContainerStats, error) {
|
|
return containerStatsWindows(), nil
|
|
},
|
|
ContainerInspectF: func(ctx context.Context, containerID string) (types.ContainerJSON, error) {
|
|
return containerInspect(), nil
|
|
},
|
|
ServiceListF: func(context.Context, types.ServiceListOptions) ([]swarm.Service, error) {
|
|
return ServiceList, nil
|
|
},
|
|
TaskListF: func(context.Context, types.TaskListOptions) ([]swarm.Task, error) {
|
|
return TaskList, nil
|
|
},
|
|
NodeListF: func(context.Context, types.NodeListOptions) ([]swarm.Node, error) {
|
|
return NodeList, nil
|
|
},
|
|
}, nil
|
|
},
|
|
}
|
|
err := d.Gather(&acc)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestContainerLabels(t *testing.T) {
|
|
var tests = []struct {
|
|
name string
|
|
container types.Container
|
|
include []string
|
|
exclude []string
|
|
expected map[string]string
|
|
}{
|
|
{
|
|
name: "Nil filters matches all",
|
|
container: genContainerLabeled(map[string]string{
|
|
"a": "x",
|
|
}),
|
|
include: nil,
|
|
exclude: nil,
|
|
expected: map[string]string{
|
|
"a": "x",
|
|
},
|
|
},
|
|
{
|
|
name: "Empty filters matches all",
|
|
container: genContainerLabeled(map[string]string{
|
|
"a": "x",
|
|
}),
|
|
include: []string{},
|
|
exclude: []string{},
|
|
expected: map[string]string{
|
|
"a": "x",
|
|
},
|
|
},
|
|
{
|
|
name: "Must match include",
|
|
container: genContainerLabeled(map[string]string{
|
|
"a": "x",
|
|
"b": "y",
|
|
}),
|
|
include: []string{"a"},
|
|
exclude: []string{},
|
|
expected: map[string]string{
|
|
"a": "x",
|
|
},
|
|
},
|
|
{
|
|
name: "Must not match exclude",
|
|
container: genContainerLabeled(map[string]string{
|
|
"a": "x",
|
|
"b": "y",
|
|
}),
|
|
include: []string{},
|
|
exclude: []string{"b"},
|
|
expected: map[string]string{
|
|
"a": "x",
|
|
},
|
|
},
|
|
{
|
|
name: "Include Glob",
|
|
container: genContainerLabeled(map[string]string{
|
|
"aa": "x",
|
|
"ab": "y",
|
|
"bb": "z",
|
|
}),
|
|
include: []string{"a*"},
|
|
exclude: []string{},
|
|
expected: map[string]string{
|
|
"aa": "x",
|
|
"ab": "y",
|
|
},
|
|
},
|
|
{
|
|
name: "Exclude Glob",
|
|
container: genContainerLabeled(map[string]string{
|
|
"aa": "x",
|
|
"ab": "y",
|
|
"bb": "z",
|
|
}),
|
|
include: []string{},
|
|
exclude: []string{"a*"},
|
|
expected: map[string]string{
|
|
"bb": "z",
|
|
},
|
|
},
|
|
{
|
|
name: "Excluded Includes",
|
|
container: genContainerLabeled(map[string]string{
|
|
"aa": "x",
|
|
"ab": "y",
|
|
"bb": "z",
|
|
}),
|
|
include: []string{"a*"},
|
|
exclude: []string{"*b"},
|
|
expected: map[string]string{
|
|
"aa": "x",
|
|
},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
var acc testutil.Accumulator
|
|
|
|
newClientFunc := func(host string, tlsConfig *tls.Config) (Client, error) {
|
|
client := baseClient
|
|
client.ContainerListF = func(context.Context, types.ContainerListOptions) ([]types.Container, error) {
|
|
return []types.Container{tt.container}, nil
|
|
}
|
|
return &client, nil
|
|
}
|
|
|
|
d := Docker{
|
|
Log: testutil.Logger{},
|
|
newClient: newClientFunc,
|
|
LabelInclude: tt.include,
|
|
LabelExclude: tt.exclude,
|
|
}
|
|
|
|
err := d.Gather(&acc)
|
|
require.NoError(t, err)
|
|
|
|
// Grab tags from a container metric
|
|
var actual map[string]string
|
|
for _, metric := range acc.Metrics {
|
|
if metric.Measurement == "docker_container_cpu" {
|
|
actual = metric.Tags
|
|
}
|
|
}
|
|
|
|
for k, v := range tt.expected {
|
|
require.Equal(t, v, actual[k])
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func genContainerLabeled(labels map[string]string) types.Container {
|
|
c := containerList[0]
|
|
c.Labels = labels
|
|
return c
|
|
}
|
|
|
|
func TestContainerNames(t *testing.T) {
|
|
var tests = []struct {
|
|
name string
|
|
containers [][]string
|
|
include []string
|
|
exclude []string
|
|
expected []string
|
|
}{
|
|
{
|
|
name: "Nil filters matches all",
|
|
include: nil,
|
|
exclude: nil,
|
|
expected: []string{"etcd", "etcd2", "acme", "acme-test", "foo"},
|
|
},
|
|
{
|
|
name: "Empty filters matches all",
|
|
include: []string{},
|
|
exclude: []string{},
|
|
expected: []string{"etcd", "etcd2", "acme", "acme-test", "foo"},
|
|
},
|
|
{
|
|
name: "Match all containers",
|
|
include: []string{"*"},
|
|
exclude: []string{},
|
|
expected: []string{"etcd", "etcd2", "acme", "acme-test", "foo"},
|
|
},
|
|
{
|
|
name: "Include prefix match",
|
|
include: []string{"etc*"},
|
|
exclude: []string{},
|
|
expected: []string{"etcd", "etcd2"},
|
|
},
|
|
{
|
|
name: "Exact match",
|
|
include: []string{"etcd"},
|
|
exclude: []string{},
|
|
expected: []string{"etcd"},
|
|
},
|
|
{
|
|
name: "Star matches zero length",
|
|
include: []string{"etcd2*"},
|
|
exclude: []string{},
|
|
expected: []string{"etcd2"},
|
|
},
|
|
{
|
|
name: "Exclude matches all",
|
|
include: []string{},
|
|
exclude: []string{"etc*"},
|
|
expected: []string{"acme", "acme-test", "foo"},
|
|
},
|
|
{
|
|
name: "Exclude single",
|
|
include: []string{},
|
|
exclude: []string{"etcd"},
|
|
expected: []string{"etcd2", "acme", "acme-test", "foo"},
|
|
},
|
|
{
|
|
name: "Exclude all",
|
|
include: []string{"*"},
|
|
exclude: []string{"*"},
|
|
expected: []string{},
|
|
},
|
|
{
|
|
name: "Exclude item matching include",
|
|
include: []string{"acme*"},
|
|
exclude: []string{"*test*"},
|
|
expected: []string{"acme"},
|
|
},
|
|
{
|
|
name: "Exclude item no wildcards",
|
|
include: []string{"acme*"},
|
|
exclude: []string{"test"},
|
|
expected: []string{"acme", "acme-test"},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
var acc testutil.Accumulator
|
|
|
|
newClientFunc := func(host string, tlsConfig *tls.Config) (Client, error) {
|
|
client := baseClient
|
|
client.ContainerListF = func(context.Context, types.ContainerListOptions) ([]types.Container, error) {
|
|
return containerList, nil
|
|
}
|
|
client.ContainerStatsF = func(c context.Context, s string, b bool) (types.ContainerStats, error) {
|
|
return containerStats(s), nil
|
|
}
|
|
|
|
return &client, nil
|
|
}
|
|
|
|
d := Docker{
|
|
Log: testutil.Logger{},
|
|
newClient: newClientFunc,
|
|
ContainerInclude: tt.include,
|
|
ContainerExclude: tt.exclude,
|
|
}
|
|
|
|
err := d.Gather(&acc)
|
|
require.NoError(t, err)
|
|
|
|
// Set of expected names
|
|
var expected = make(map[string]bool)
|
|
for _, v := range tt.expected {
|
|
expected[v] = true
|
|
}
|
|
|
|
// Set of actual names
|
|
var actual = make(map[string]bool)
|
|
for _, metric := range acc.Metrics {
|
|
if name, ok := metric.Tags["container_name"]; ok {
|
|
actual[name] = true
|
|
}
|
|
}
|
|
|
|
require.Equal(t, expected, actual)
|
|
})
|
|
}
|
|
}
|
|
|
|
func FilterMetrics(metrics []telegraf.Metric, f func(telegraf.Metric) bool) []telegraf.Metric {
|
|
results := []telegraf.Metric{}
|
|
for _, m := range metrics {
|
|
if f(m) {
|
|
results = append(results, m)
|
|
}
|
|
}
|
|
return results
|
|
}
|
|
|
|
func TestContainerStatus(t *testing.T) {
|
|
var tests = []struct {
|
|
name string
|
|
now func() time.Time
|
|
inspect types.ContainerJSON
|
|
expected []telegraf.Metric
|
|
}{
|
|
{
|
|
name: "finished_at is zero value",
|
|
now: func() time.Time {
|
|
return time.Date(2018, 6, 14, 5, 51, 53, 266176036, time.UTC)
|
|
},
|
|
inspect: containerInspect(),
|
|
expected: []telegraf.Metric{
|
|
testutil.MustMetric(
|
|
"docker_container_status",
|
|
map[string]string{
|
|
"container_name": "etcd",
|
|
"container_image": "quay.io/coreos/etcd",
|
|
"container_version": "v2.2.2",
|
|
"engine_host": "absol",
|
|
"label1": "test_value_1",
|
|
"label2": "test_value_2",
|
|
"server_version": "17.09.0-ce",
|
|
"container_status": "running",
|
|
"source": "e2173b9478a6",
|
|
},
|
|
map[string]interface{}{
|
|
"oomkilled": false,
|
|
"pid": 1234,
|
|
"exitcode": 0,
|
|
"container_id": "e2173b9478a6ae55e237d4d74f8bbb753f0817192b5081334dc78476296b7dfb",
|
|
"started_at": time.Date(2018, 6, 14, 5, 48, 53, 266176036, time.UTC).UnixNano(),
|
|
"uptime_ns": int64(3 * time.Minute),
|
|
},
|
|
time.Date(2018, 6, 14, 5, 51, 53, 266176036, time.UTC),
|
|
),
|
|
},
|
|
},
|
|
{
|
|
name: "finished_at is non-zero value",
|
|
now: func() time.Time {
|
|
return time.Date(2018, 6, 14, 5, 51, 53, 266176036, time.UTC)
|
|
},
|
|
inspect: func() types.ContainerJSON {
|
|
i := containerInspect()
|
|
i.ContainerJSONBase.State.FinishedAt = "2018-06-14T05:53:53.266176036Z"
|
|
return i
|
|
}(),
|
|
expected: []telegraf.Metric{
|
|
testutil.MustMetric(
|
|
"docker_container_status",
|
|
map[string]string{
|
|
"container_name": "etcd",
|
|
"container_image": "quay.io/coreos/etcd",
|
|
"container_version": "v2.2.2",
|
|
"engine_host": "absol",
|
|
"label1": "test_value_1",
|
|
"label2": "test_value_2",
|
|
"server_version": "17.09.0-ce",
|
|
"container_status": "running",
|
|
"source": "e2173b9478a6",
|
|
},
|
|
map[string]interface{}{
|
|
"oomkilled": false,
|
|
"pid": 1234,
|
|
"exitcode": 0,
|
|
"container_id": "e2173b9478a6ae55e237d4d74f8bbb753f0817192b5081334dc78476296b7dfb",
|
|
"started_at": time.Date(2018, 6, 14, 5, 48, 53, 266176036, time.UTC).UnixNano(),
|
|
"finished_at": time.Date(2018, 6, 14, 5, 53, 53, 266176036, time.UTC).UnixNano(),
|
|
"uptime_ns": int64(5 * time.Minute),
|
|
},
|
|
time.Date(2018, 6, 14, 5, 51, 53, 266176036, time.UTC),
|
|
),
|
|
},
|
|
},
|
|
{
|
|
name: "started_at is zero value",
|
|
now: func() time.Time {
|
|
return time.Date(2018, 6, 14, 5, 51, 53, 266176036, time.UTC)
|
|
},
|
|
inspect: func() types.ContainerJSON {
|
|
i := containerInspect()
|
|
i.ContainerJSONBase.State.StartedAt = ""
|
|
i.ContainerJSONBase.State.FinishedAt = "2018-06-14T05:53:53.266176036Z"
|
|
return i
|
|
}(),
|
|
expected: []telegraf.Metric{
|
|
testutil.MustMetric(
|
|
"docker_container_status",
|
|
map[string]string{
|
|
"container_name": "etcd",
|
|
"container_image": "quay.io/coreos/etcd",
|
|
"container_version": "v2.2.2",
|
|
"engine_host": "absol",
|
|
"label1": "test_value_1",
|
|
"label2": "test_value_2",
|
|
"server_version": "17.09.0-ce",
|
|
"container_status": "running",
|
|
"source": "e2173b9478a6",
|
|
},
|
|
map[string]interface{}{
|
|
"oomkilled": false,
|
|
"pid": 1234,
|
|
"exitcode": 0,
|
|
"container_id": "e2173b9478a6ae55e237d4d74f8bbb753f0817192b5081334dc78476296b7dfb",
|
|
"finished_at": time.Date(2018, 6, 14, 5, 53, 53, 266176036, time.UTC).UnixNano(),
|
|
},
|
|
time.Date(2018, 6, 14, 5, 51, 53, 266176036, time.UTC),
|
|
),
|
|
},
|
|
},
|
|
{
|
|
name: "container has been restarted",
|
|
now: func() time.Time {
|
|
return time.Date(2019, 1, 1, 0, 0, 3, 0, time.UTC)
|
|
},
|
|
inspect: func() types.ContainerJSON {
|
|
i := containerInspect()
|
|
i.ContainerJSONBase.State.StartedAt = "2019-01-01T00:00:02Z"
|
|
i.ContainerJSONBase.State.FinishedAt = "2019-01-01T00:00:01Z"
|
|
return i
|
|
}(),
|
|
expected: []telegraf.Metric{
|
|
testutil.MustMetric(
|
|
"docker_container_status",
|
|
map[string]string{
|
|
"container_name": "etcd",
|
|
"container_image": "quay.io/coreos/etcd",
|
|
"container_version": "v2.2.2",
|
|
"engine_host": "absol",
|
|
"label1": "test_value_1",
|
|
"label2": "test_value_2",
|
|
"server_version": "17.09.0-ce",
|
|
"container_status": "running",
|
|
"source": "e2173b9478a6",
|
|
},
|
|
map[string]interface{}{
|
|
"oomkilled": false,
|
|
"pid": 1234,
|
|
"exitcode": 0,
|
|
"container_id": "e2173b9478a6ae55e237d4d74f8bbb753f0817192b5081334dc78476296b7dfb",
|
|
"started_at": time.Date(2019, 1, 1, 0, 0, 2, 0, time.UTC).UnixNano(),
|
|
"finished_at": time.Date(2019, 1, 1, 0, 0, 1, 0, time.UTC).UnixNano(),
|
|
"uptime_ns": int64(1 * time.Second),
|
|
},
|
|
time.Date(2019, 1, 1, 0, 0, 3, 0, time.UTC),
|
|
),
|
|
},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
var (
|
|
acc testutil.Accumulator
|
|
newClientFunc = func(string, *tls.Config) (Client, error) {
|
|
client := baseClient
|
|
client.ContainerListF = func(context.Context, types.ContainerListOptions) ([]types.Container, error) {
|
|
return containerList[:1], nil
|
|
}
|
|
client.ContainerInspectF = func(c context.Context, s string) (types.ContainerJSON, error) {
|
|
return tt.inspect, nil
|
|
}
|
|
|
|
return &client, nil
|
|
}
|
|
d = Docker{
|
|
Log: testutil.Logger{},
|
|
newClient: newClientFunc,
|
|
IncludeSourceTag: true,
|
|
}
|
|
)
|
|
|
|
// mock time
|
|
if tt.now != nil {
|
|
now = tt.now
|
|
}
|
|
defer func() {
|
|
now = time.Now
|
|
}()
|
|
|
|
err := d.Gather(&acc)
|
|
require.NoError(t, err)
|
|
|
|
actual := FilterMetrics(acc.GetTelegrafMetrics(), func(m telegraf.Metric) bool {
|
|
return m.Name() == "docker_container_status"
|
|
})
|
|
testutil.RequireMetricsEqual(t, tt.expected, actual)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDockerGatherInfo(t *testing.T) {
|
|
var acc testutil.Accumulator
|
|
d := Docker{
|
|
Log: testutil.Logger{},
|
|
newClient: newClient,
|
|
TagEnvironment: []string{"ENVVAR1", "ENVVAR2", "ENVVAR3", "ENVVAR5",
|
|
"ENVVAR6", "ENVVAR7", "ENVVAR8", "ENVVAR9"},
|
|
}
|
|
|
|
err := acc.GatherError(d.Gather)
|
|
require.NoError(t, err)
|
|
|
|
acc.AssertContainsTaggedFields(t,
|
|
"docker",
|
|
map[string]interface{}{
|
|
"n_listener_events": int(0),
|
|
"n_cpus": int(4),
|
|
"n_used_file_descriptors": int(19),
|
|
"n_containers": int(108),
|
|
"n_containers_running": int(98),
|
|
"n_containers_stopped": int(6),
|
|
"n_containers_paused": int(3),
|
|
"n_images": int(199),
|
|
"n_goroutines": int(39),
|
|
},
|
|
map[string]string{
|
|
"engine_host": "absol",
|
|
"server_version": "17.09.0-ce",
|
|
},
|
|
)
|
|
|
|
acc.AssertContainsTaggedFields(t,
|
|
"docker",
|
|
map[string]interface{}{
|
|
"memory_total": int64(3840757760),
|
|
},
|
|
map[string]string{
|
|
"engine_host": "absol",
|
|
"server_version": "17.09.0-ce",
|
|
},
|
|
)
|
|
|
|
acc.AssertContainsTaggedFields(t,
|
|
"docker",
|
|
map[string]interface{}{
|
|
"pool_blocksize": int64(65540),
|
|
},
|
|
map[string]string{
|
|
"engine_host": "absol",
|
|
"server_version": "17.09.0-ce",
|
|
"unit": "bytes",
|
|
},
|
|
)
|
|
|
|
acc.AssertContainsTaggedFields(t,
|
|
"docker_data",
|
|
map[string]interface{}{
|
|
"used": int64(17300000000),
|
|
"total": int64(107400000000),
|
|
"available": int64(36530000000),
|
|
},
|
|
map[string]string{
|
|
"engine_host": "absol",
|
|
"server_version": "17.09.0-ce",
|
|
"unit": "bytes",
|
|
},
|
|
)
|
|
|
|
acc.AssertContainsTaggedFields(t,
|
|
"docker_metadata",
|
|
map[string]interface{}{
|
|
"used": int64(20970000),
|
|
"total": int64(2146999999),
|
|
"available": int64(2126999999),
|
|
},
|
|
map[string]string{
|
|
"engine_host": "absol",
|
|
"server_version": "17.09.0-ce",
|
|
"unit": "bytes",
|
|
},
|
|
)
|
|
|
|
acc.AssertContainsTaggedFields(t,
|
|
"docker_devicemapper",
|
|
map[string]interface{}{
|
|
"base_device_size_bytes": int64(10740000000),
|
|
"pool_blocksize_bytes": int64(65540),
|
|
"data_space_used_bytes": int64(17300000000),
|
|
"data_space_total_bytes": int64(107400000000),
|
|
"data_space_available_bytes": int64(36530000000),
|
|
"metadata_space_used_bytes": int64(20970000),
|
|
"metadata_space_total_bytes": int64(2146999999),
|
|
"metadata_space_available_bytes": int64(2126999999),
|
|
"thin_pool_minimum_free_space_bytes": int64(10740000000),
|
|
},
|
|
map[string]string{
|
|
"engine_host": "absol",
|
|
"server_version": "17.09.0-ce",
|
|
"pool_name": "docker-8:1-1182287-pool",
|
|
},
|
|
)
|
|
|
|
acc.AssertContainsTaggedFields(t,
|
|
"docker_container_cpu",
|
|
map[string]interface{}{
|
|
"usage_total": uint64(1231652),
|
|
"container_id": "b7dfbb9478a6ae55e237d4d74f8bbb753f0817192b5081334dc78476296e2173",
|
|
},
|
|
map[string]string{
|
|
"container_name": "etcd2",
|
|
"container_image": "quay.io:4443/coreos/etcd",
|
|
"cpu": "cpu3",
|
|
"container_version": "v2.2.2",
|
|
"engine_host": "absol",
|
|
"ENVVAR1": "loremipsum",
|
|
"ENVVAR2": "dolorsitamet",
|
|
"ENVVAR3": "=ubuntu:10.04",
|
|
"ENVVAR7": "ENVVAR8=ENVVAR9",
|
|
"label1": "test_value_1",
|
|
"label2": "test_value_2",
|
|
"server_version": "17.09.0-ce",
|
|
"container_status": "running",
|
|
},
|
|
)
|
|
acc.AssertContainsTaggedFields(t,
|
|
"docker_container_mem",
|
|
map[string]interface{}{
|
|
"container_id": "b7dfbb9478a6ae55e237d4d74f8bbb753f0817192b5081334dc78476296e2173",
|
|
"limit": uint64(18935443456),
|
|
"max_usage": uint64(0),
|
|
"usage": uint64(0),
|
|
"usage_percent": float64(0),
|
|
},
|
|
map[string]string{
|
|
"engine_host": "absol",
|
|
"container_name": "etcd2",
|
|
"container_image": "quay.io:4443/coreos/etcd",
|
|
"container_version": "v2.2.2",
|
|
"ENVVAR1": "loremipsum",
|
|
"ENVVAR2": "dolorsitamet",
|
|
"ENVVAR3": "=ubuntu:10.04",
|
|
"ENVVAR7": "ENVVAR8=ENVVAR9",
|
|
"label1": "test_value_1",
|
|
"label2": "test_value_2",
|
|
"server_version": "17.09.0-ce",
|
|
"container_status": "running",
|
|
},
|
|
)
|
|
}
|
|
|
|
func TestDockerGatherSwarmInfo(t *testing.T) {
|
|
var acc testutil.Accumulator
|
|
d := Docker{
|
|
Log: testutil.Logger{},
|
|
newClient: newClient,
|
|
}
|
|
|
|
err := acc.GatherError(d.Gather)
|
|
require.NoError(t, err)
|
|
|
|
d.gatherSwarmInfo(&acc)
|
|
|
|
// test docker_container_net measurement
|
|
acc.AssertContainsTaggedFields(t,
|
|
"docker_swarm",
|
|
map[string]interface{}{
|
|
"tasks_running": int(2),
|
|
"tasks_desired": uint64(2),
|
|
},
|
|
map[string]string{
|
|
"service_id": "qolkls9g5iasdiuihcyz9rnx2",
|
|
"service_name": "test1",
|
|
"service_mode": "replicated",
|
|
},
|
|
)
|
|
|
|
acc.AssertContainsTaggedFields(t,
|
|
"docker_swarm",
|
|
map[string]interface{}{
|
|
"tasks_running": int(1),
|
|
"tasks_desired": int(1),
|
|
},
|
|
map[string]string{
|
|
"service_id": "qolkls9g5iasdiuihcyz9rn3",
|
|
"service_name": "test2",
|
|
"service_mode": "global",
|
|
},
|
|
)
|
|
}
|
|
|
|
func TestContainerStateFilter(t *testing.T) {
|
|
var tests = []struct {
|
|
name string
|
|
include []string
|
|
exclude []string
|
|
expected map[string][]string
|
|
}{
|
|
{
|
|
name: "default",
|
|
expected: map[string][]string{
|
|
"status": {"running"},
|
|
},
|
|
},
|
|
{
|
|
name: "include running",
|
|
include: []string{"running"},
|
|
expected: map[string][]string{
|
|
"status": {"running"},
|
|
},
|
|
},
|
|
{
|
|
name: "include glob",
|
|
include: []string{"r*"},
|
|
expected: map[string][]string{
|
|
"status": {"restarting", "running", "removing"},
|
|
},
|
|
},
|
|
{
|
|
name: "include all",
|
|
include: []string{"*"},
|
|
expected: map[string][]string{
|
|
"status": {"created", "restarting", "running", "removing", "paused", "exited", "dead"},
|
|
},
|
|
},
|
|
{
|
|
name: "exclude all",
|
|
exclude: []string{"*"},
|
|
expected: map[string][]string{
|
|
"status": {},
|
|
},
|
|
},
|
|
{
|
|
name: "exclude all",
|
|
include: []string{"*"},
|
|
exclude: []string{"exited"},
|
|
expected: map[string][]string{
|
|
"status": {"created", "restarting", "running", "removing", "paused", "dead"},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
var acc testutil.Accumulator
|
|
|
|
newClientFunc := func(host string, tlsConfig *tls.Config) (Client, error) {
|
|
client := baseClient
|
|
client.ContainerListF = func(ctx context.Context, options types.ContainerListOptions) ([]types.Container, error) {
|
|
for k, v := range tt.expected {
|
|
actual := options.Filters.Get(k)
|
|
sort.Strings(actual)
|
|
sort.Strings(v)
|
|
require.Equal(t, v, actual)
|
|
}
|
|
|
|
return nil, nil
|
|
}
|
|
return &client, nil
|
|
}
|
|
|
|
d := Docker{
|
|
Log: testutil.Logger{},
|
|
newClient: newClientFunc,
|
|
ContainerStateInclude: tt.include,
|
|
ContainerStateExclude: tt.exclude,
|
|
}
|
|
|
|
err := d.Gather(&acc)
|
|
require.NoError(t, err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestContainerName(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
clientFunc func(host string, tlsConfig *tls.Config) (Client, error)
|
|
expected string
|
|
}{
|
|
{
|
|
name: "container stats name is preferred",
|
|
clientFunc: func(host string, tlsConfig *tls.Config) (Client, error) {
|
|
client := baseClient
|
|
client.ContainerListF = func(context.Context, types.ContainerListOptions) ([]types.Container, error) {
|
|
var containers []types.Container
|
|
containers = append(containers, types.Container{
|
|
Names: []string{"/logspout/foo"},
|
|
})
|
|
return containers, nil
|
|
}
|
|
client.ContainerStatsF = func(ctx context.Context, containerID string, stream bool) (types.ContainerStats, error) {
|
|
return types.ContainerStats{
|
|
Body: ioutil.NopCloser(strings.NewReader(`{"name": "logspout"}`)),
|
|
}, nil
|
|
}
|
|
return &client, nil
|
|
},
|
|
expected: "logspout",
|
|
},
|
|
{
|
|
name: "container stats without name uses container list name",
|
|
clientFunc: func(host string, tlsConfig *tls.Config) (Client, error) {
|
|
client := baseClient
|
|
client.ContainerListF = func(context.Context, types.ContainerListOptions) ([]types.Container, error) {
|
|
var containers []types.Container
|
|
containers = append(containers, types.Container{
|
|
Names: []string{"/logspout"},
|
|
})
|
|
return containers, nil
|
|
}
|
|
client.ContainerStatsF = func(ctx context.Context, containerID string, stream bool) (types.ContainerStats, error) {
|
|
return types.ContainerStats{
|
|
Body: ioutil.NopCloser(strings.NewReader(`{}`)),
|
|
}, nil
|
|
}
|
|
return &client, nil
|
|
},
|
|
expected: "logspout",
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
d := Docker{
|
|
Log: testutil.Logger{},
|
|
newClient: tt.clientFunc,
|
|
}
|
|
var acc testutil.Accumulator
|
|
err := d.Gather(&acc)
|
|
require.NoError(t, err)
|
|
|
|
for _, metric := range acc.Metrics {
|
|
// This tag is set on all container measurements
|
|
if metric.Measurement == "docker_container_mem" {
|
|
require.Equal(t, tt.expected, metric.Tags["container_name"])
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHostnameFromID(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
id string
|
|
expect string
|
|
}{
|
|
{
|
|
name: "Real ID",
|
|
id: "565e3a55f5843cfdd4aa5659a1a75e4e78d47f73c3c483f782fe4a26fc8caa07",
|
|
expect: "565e3a55f584",
|
|
},
|
|
{
|
|
name: "Short ID",
|
|
id: "shortid123",
|
|
expect: "shortid123",
|
|
},
|
|
{
|
|
name: "No ID",
|
|
id: "",
|
|
expect: "shortid123",
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
output := hostnameFromID(test.id)
|
|
if test.expect != output {
|
|
t.Logf("Container ID for hostname is wrong. Want: %s, Got: %s", output, test.expect)
|
|
}
|
|
})
|
|
}
|
|
|
|
}
|