package dcos import ( "context" "fmt" "testing" "github.com/influxdata/telegraf/testutil" "github.com/stretchr/testify/require" ) type mockClient struct { SetTokenF func(token string) LoginF func(ctx context.Context, sa *ServiceAccount) (*AuthToken, error) GetSummaryF func(ctx context.Context) (*Summary, error) GetContainersF func(ctx context.Context, node string) ([]Container, error) GetNodeMetricsF func(ctx context.Context, node string) (*Metrics, error) GetContainerMetricsF func(ctx context.Context, node, container string) (*Metrics, error) GetAppMetricsF func(ctx context.Context, node, container string) (*Metrics, error) } func (c *mockClient) SetToken(token string) { c.SetTokenF(token) } func (c *mockClient) Login(ctx context.Context, sa *ServiceAccount) (*AuthToken, error) { return c.LoginF(ctx, sa) } func (c *mockClient) GetSummary(ctx context.Context) (*Summary, error) { return c.GetSummaryF(ctx) } func (c *mockClient) GetContainers(ctx context.Context, node string) ([]Container, error) { return c.GetContainersF(ctx, node) } func (c *mockClient) GetNodeMetrics(ctx context.Context, node string) (*Metrics, error) { return c.GetNodeMetricsF(ctx, node) } func (c *mockClient) GetContainerMetrics(ctx context.Context, node, container string) (*Metrics, error) { return c.GetContainerMetricsF(ctx, node, container) } func (c *mockClient) GetAppMetrics(ctx context.Context, node, container string) (*Metrics, error) { return c.GetAppMetricsF(ctx, node, container) } func TestAddNodeMetrics(t *testing.T) { var tests = []struct { name string metrics *Metrics check func(*testutil.Accumulator) []bool }{ { name: "basic datapoint conversion", metrics: &Metrics{ Datapoints: []DataPoint{ { Name: "process.count", Unit: "count", Value: 42.0, }, }, }, check: func(acc *testutil.Accumulator) []bool { return []bool{acc.HasPoint( "dcos_node", map[string]string{ "cluster": "a", }, "process_count", 42.0, )} }, }, { name: "path added as tag", metrics: &Metrics{ Datapoints: []DataPoint{ { Name: "filesystem.inode.free", Tags: map[string]string{ "path": "/var/lib", }, Unit: "count", Value: 42.0, }, }, }, check: func(acc *testutil.Accumulator) []bool { return []bool{acc.HasPoint( "dcos_node", map[string]string{ "cluster": "a", "path": "/var/lib", }, "filesystem_inode_free", 42.0, )} }, }, { name: "interface added as tag", metrics: &Metrics{ Datapoints: []DataPoint{ { Name: "network.out.dropped", Tags: map[string]string{ "interface": "eth0", }, Unit: "count", Value: 42.0, }, }, }, check: func(acc *testutil.Accumulator) []bool { return []bool{acc.HasPoint( "dcos_node", map[string]string{ "cluster": "a", "interface": "eth0", }, "network_out_dropped", 42.0, )} }, }, { name: "bytes unit appended to fieldkey", metrics: &Metrics{ Datapoints: []DataPoint{ { Name: "network.in", Tags: map[string]string{ "interface": "eth0", }, Unit: "bytes", Value: 42.0, }, }, }, check: func(acc *testutil.Accumulator) []bool { return []bool{acc.HasPoint( "dcos_node", map[string]string{ "cluster": "a", "interface": "eth0", }, "network_in_bytes", int64(42), )} }, }, { name: "dimensions added as tags", metrics: &Metrics{ Datapoints: []DataPoint{ { Name: "process.count", Tags: map[string]string{}, Unit: "count", Value: 42.0, }, { Name: "memory.total", Tags: map[string]string{}, Unit: "bytes", Value: 42, }, }, Dimensions: map[string]interface{}{ "cluster_id": "c0760bbd-9e9d-434b-bd4a-39c7cdef8a63", "hostname": "192.168.122.18", "mesos_id": "2dfbbd28-29d2-411d-92c4-e2f84c38688e-S1", }, }, check: func(acc *testutil.Accumulator) []bool { return []bool{ acc.HasPoint( "dcos_node", map[string]string{ "cluster": "a", "hostname": "192.168.122.18", }, "process_count", 42.0), acc.HasPoint( "dcos_node", map[string]string{ "cluster": "a", "hostname": "192.168.122.18", }, "memory_total_bytes", int64(42)), } }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var acc testutil.Accumulator dcos := &DCOS{} dcos.addNodeMetrics(&acc, "a", tt.metrics) for i, ok := range tt.check(&acc) { require.True(t, ok, fmt.Sprintf("Index was not true: %d", i)) } }) } } func TestAddContainerMetrics(t *testing.T) { var tests = []struct { name string metrics *Metrics check func(*testutil.Accumulator) []bool }{ { name: "container", metrics: &Metrics{ Datapoints: []DataPoint{ { Name: "net.rx.errors", Tags: map[string]string{ "container_id": "f25c457b-fceb-44f0-8f5b-38be34cbb6fb", "executor_id": "telegraf.192fb45f-cc0c-11e7-af48-ea183c0b541a", "executor_name": "Command Executor (Task: telegraf.192fb45f-cc0c-11e7-af48-ea183c0b541a) (Command: NO EXECUTABLE)", "framework_id": "ab2f3a8b-06db-4e8c-95b6-fb1940874a30-0001", "source": "telegraf.192fb45f-cc0c-11e7-af48-ea183c0b541a", }, Unit: "count", Value: 42.0, }, }, Dimensions: map[string]interface{}{ "cluster_id": "c0760bbd-9e9d-434b-bd4a-39c7cdef8a63", "container_id": "f25c457b-fceb-44f0-8f5b-38be34cbb6fb", "executor_id": "telegraf.192fb45f-cc0c-11e7-af48-ea183c0b541a", "framework_id": "ab2f3a8b-06db-4e8c-95b6-fb1940874a30-0001", "framework_name": "marathon", "framework_principal": "dcos_marathon", "framework_role": "slave_public", "hostname": "192.168.122.18", "labels": map[string]string{ "DCOS_SPACE": "/telegraf", }, "mesos_id": "2dfbbd28-29d2-411d-92c4-e2f84c38688e-S1", "task_id": "telegraf.192fb45f-cc0c-11e7-af48-ea183c0b541a", "task_name": "telegraf", }, }, check: func(acc *testutil.Accumulator) []bool { return []bool{ acc.HasPoint( "dcos_container", map[string]string{ "cluster": "a", "container_id": "f25c457b-fceb-44f0-8f5b-38be34cbb6fb", "hostname": "192.168.122.18", "task_name": "telegraf", "DCOS_SPACE": "/telegraf", }, "net_rx_errors", 42.0, ), } }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var acc testutil.Accumulator dcos := &DCOS{} dcos.addContainerMetrics(&acc, "a", tt.metrics) for i, ok := range tt.check(&acc) { require.True(t, ok, fmt.Sprintf("Index was not true: %d", i)) } }) } } func TestAddAppMetrics(t *testing.T) { var tests = []struct { name string metrics *Metrics check func(*testutil.Accumulator) []bool }{ { name: "tags are optional", metrics: &Metrics{ Datapoints: []DataPoint{ { Name: "dcos.metrics.module.container_throttled_bytes_per_sec", Unit: "", Value: 42.0, }, }, }, check: func(acc *testutil.Accumulator) []bool { return []bool{ acc.HasPoint( "dcos_app", map[string]string{ "cluster": "a", }, "container_throttled_bytes_per_sec", 42.0, ), } }, }, { name: "dimensions are tagged", metrics: &Metrics{ Datapoints: []DataPoint{ { Name: "dcos.metrics.module.container_throttled_bytes_per_sec", Unit: "", Value: 42.0, }, }, Dimensions: map[string]interface{}{ "cluster_id": "c0760bbd-9e9d-434b-bd4a-39c7cdef8a63", "container_id": "02d31175-1c01-4459-8520-ef8b1339bc52", "hostname": "192.168.122.18", "mesos_id": "2dfbbd28-29d2-411d-92c4-e2f84c38688e-S1", }, }, check: func(acc *testutil.Accumulator) []bool { return []bool{ acc.HasPoint( "dcos_app", map[string]string{ "cluster": "a", "container_id": "02d31175-1c01-4459-8520-ef8b1339bc52", "hostname": "192.168.122.18", }, "container_throttled_bytes_per_sec", 42.0, ), } }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var acc testutil.Accumulator dcos := &DCOS{} dcos.addAppMetrics(&acc, "a", tt.metrics) for i, ok := range tt.check(&acc) { require.True(t, ok, fmt.Sprintf("Index was not true: %d", i)) } }) } } func TestGatherFilterNode(t *testing.T) { var tests = []struct { name string nodeInclude []string nodeExclude []string client Client check func(*testutil.Accumulator) []bool }{ { name: "cluster without nodes has no metrics", client: &mockClient{ SetTokenF: func(token string) {}, GetSummaryF: func(ctx context.Context) (*Summary, error) { return &Summary{ Cluster: "a", Slaves: []Slave{}, }, nil }, }, check: func(acc *testutil.Accumulator) []bool { return []bool{ acc.NMetrics() == 0, } }, }, { name: "node include", nodeInclude: []string{"x"}, client: &mockClient{ SetTokenF: func(token string) {}, GetSummaryF: func(ctx context.Context) (*Summary, error) { return &Summary{ Cluster: "a", Slaves: []Slave{ Slave{ID: "x"}, Slave{ID: "y"}, }, }, nil }, GetContainersF: func(ctx context.Context, node string) ([]Container, error) { return []Container{}, nil }, GetNodeMetricsF: func(ctx context.Context, node string) (*Metrics, error) { return &Metrics{ Datapoints: []DataPoint{ { Name: "value", Value: 42.0, }, }, Dimensions: map[string]interface{}{ "hostname": "x", }, }, nil }, }, check: func(acc *testutil.Accumulator) []bool { return []bool{ acc.HasPoint( "dcos_node", map[string]string{ "cluster": "a", "hostname": "x", }, "value", 42.0, ), acc.NMetrics() == 1, } }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var acc testutil.Accumulator dcos := &DCOS{ NodeInclude: tt.nodeInclude, NodeExclude: tt.nodeExclude, client: tt.client, } err := dcos.Gather(&acc) require.NoError(t, err) for i, ok := range tt.check(&acc) { require.True(t, ok, fmt.Sprintf("Index was not true: %d", i)) } }) } }