// SPDX-License-Identifier: GPL-3.0-or-later package docker_engine import ( "net/http" "net/http/httptest" "os" "testing" "github.com/netdata/netdata/go/go.d.plugin/agent/module" "github.com/netdata/netdata/go/go.d.plugin/pkg/tlscfg" "github.com/netdata/netdata/go/go.d.plugin/pkg/web" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) var ( dataConfigJSON, _ = os.ReadFile("testdata/config.json") dataConfigYAML, _ = os.ReadFile("testdata/config.yaml") dataNonDockerEngineMetrics, _ = os.ReadFile("testdata/non-docker-engine.txt") dataVer17050Metrics, _ = os.ReadFile("testdata/v17.05.0-ce.txt") dataVer18093Metrics, _ = os.ReadFile("testdata/v18.09.3-ce.txt") dataVer18093SwarmMetrics, _ = os.ReadFile("testdata/v18.09.3-ce-swarm.txt") ) func Test_testDataIsValid(t *testing.T) { for name, data := range map[string][]byte{ "dataConfigJSON": dataConfigJSON, "dataConfigYAML": dataConfigYAML, "dataNonDockerEngineMetrics": dataNonDockerEngineMetrics, "dataVer17050Metrics": dataVer17050Metrics, "dataVer18093Metrics": dataVer18093Metrics, "dataVer18093SwarmMetrics": dataVer18093SwarmMetrics, } { require.NotNil(t, data, name) } } func TestDockerEngine_ConfigurationSerialize(t *testing.T) { module.TestConfigurationSerialize(t, &DockerEngine{}, dataConfigJSON, dataConfigYAML) } func TestDockerEngine_Cleanup(t *testing.T) { assert.NotPanics(t, New().Cleanup) } func TestDockerEngine_Init(t *testing.T) { tests := map[string]struct { config Config wantFail bool }{ "default": { config: New().Config, }, "empty URL": { config: Config{HTTP: web.HTTP{Request: web.Request{URL: ""}}}, wantFail: true, }, "nonexistent TLS CA": { config: Config{HTTP: web.HTTP{ Request: web.Request{URL: "http://127.0.0.1:9323/metrics"}, Client: web.Client{TLSConfig: tlscfg.TLSConfig{TLSCA: "testdata/tls"}}}}, wantFail: true, }, } for name, test := range tests { t.Run(name, func(t *testing.T) { dockerEngine := New() dockerEngine.Config = test.config if test.wantFail { assert.Error(t, dockerEngine.Init()) } else { assert.NoError(t, dockerEngine.Init()) } }) } } func TestDockerEngine_Check(t *testing.T) { tests := map[string]struct { prepare func(*testing.T) (*DockerEngine, *httptest.Server) wantFail bool }{ "v17.05.0-ce": {prepare: prepareClientServerV17050CE}, "v18.09.3-ce": {prepare: prepareClientServerV18093CE}, "v18.09.3-ce-swarm": {prepare: prepareClientServerV18093CESwarm}, "non docker engine": {prepare: prepareClientServerNonDockerEngine, wantFail: true}, "invalid data": {prepare: prepareClientServerInvalidData, wantFail: true}, "404": {prepare: prepareClientServer404, wantFail: true}, "connection refused": {prepare: prepareClientServerConnectionRefused, wantFail: true}, } for name, test := range tests { t.Run(name, func(t *testing.T) { dockerEngine, srv := test.prepare(t) defer srv.Close() if test.wantFail { assert.Error(t, dockerEngine.Check()) } else { assert.NoError(t, dockerEngine.Check()) } }) } } func TestDockerEngine_Charts(t *testing.T) { tests := map[string]struct { prepare func(*testing.T) (*DockerEngine, *httptest.Server) wantNumCharts int }{ "v17.05.0-ce": {prepare: prepareClientServerV17050CE, wantNumCharts: len(charts) - 1}, // no container states chart "v18.09.3-ce": {prepare: prepareClientServerV18093CE, wantNumCharts: len(charts)}, "v18.09.3-ce-swarm": {prepare: prepareClientServerV18093CESwarm, wantNumCharts: len(charts) + len(swarmManagerCharts)}, } for name, test := range tests { t.Run(name, func(t *testing.T) { dockerEngine, srv := test.prepare(t) defer srv.Close() require.NoError(t, dockerEngine.Check()) assert.Len(t, *dockerEngine.Charts(), test.wantNumCharts) }) } } func TestDockerEngine_Collect_ReturnsNilOnErrors(t *testing.T) { tests := map[string]struct { prepare func(*testing.T) (*DockerEngine, *httptest.Server) }{ "non docker engine": {prepare: prepareClientServerNonDockerEngine}, "invalid data": {prepare: prepareClientServerInvalidData}, "404": {prepare: prepareClientServer404}, "connection refused": {prepare: prepareClientServerConnectionRefused}, } for name, test := range tests { t.Run(name, func(t *testing.T) { dockerEngine, srv := test.prepare(t) defer srv.Close() assert.Nil(t, dockerEngine.Collect()) }) } } func TestDockerEngine_Collect(t *testing.T) { tests := map[string]struct { prepare func(*testing.T) (*DockerEngine, *httptest.Server) expected map[string]int64 }{ "v17.05.0-ce": { prepare: prepareClientServerV17050CE, expected: map[string]int64{ "builder_fails_build_canceled": 1, "builder_fails_build_target_not_reachable_error": 2, "builder_fails_command_not_supported_error": 3, "builder_fails_dockerfile_empty_error": 4, "builder_fails_dockerfile_syntax_error": 5, "builder_fails_error_processing_commands_error": 6, "builder_fails_missing_onbuild_arguments_error": 7, "builder_fails_unknown_instruction_error": 8, "container_actions_changes": 1, "container_actions_commit": 1, "container_actions_create": 1, "container_actions_delete": 1, "container_actions_start": 1, "health_checks_failed": 33, }, }, "v18.09.3-ce": { prepare: prepareClientServerV18093CE, expected: map[string]int64{ "builder_fails_build_canceled": 1, "builder_fails_build_target_not_reachable_error": 2, "builder_fails_command_not_supported_error": 3, "builder_fails_dockerfile_empty_error": 4, "builder_fails_dockerfile_syntax_error": 5, "builder_fails_error_processing_commands_error": 6, "builder_fails_missing_onbuild_arguments_error": 7, "builder_fails_unknown_instruction_error": 8, "container_actions_changes": 1, "container_actions_commit": 1, "container_actions_create": 1, "container_actions_delete": 1, "container_actions_start": 1, "container_states_paused": 11, "container_states_running": 12, "container_states_stopped": 13, "health_checks_failed": 33, }, }, "v18.09.3-ce-swarm": { prepare: prepareClientServerV18093CESwarm, expected: map[string]int64{ "builder_fails_build_canceled": 1, "builder_fails_build_target_not_reachable_error": 2, "builder_fails_command_not_supported_error": 3, "builder_fails_dockerfile_empty_error": 4, "builder_fails_dockerfile_syntax_error": 5, "builder_fails_error_processing_commands_error": 6, "builder_fails_missing_onbuild_arguments_error": 7, "builder_fails_unknown_instruction_error": 8, "container_actions_changes": 1, "container_actions_commit": 1, "container_actions_create": 1, "container_actions_delete": 1, "container_actions_start": 1, "container_states_paused": 11, "container_states_running": 12, "container_states_stopped": 13, "health_checks_failed": 33, "swarm_manager_configs_total": 1, "swarm_manager_leader": 1, "swarm_manager_networks_total": 3, "swarm_manager_nodes_state_disconnected": 1, "swarm_manager_nodes_state_down": 2, "swarm_manager_nodes_state_ready": 3, "swarm_manager_nodes_state_unknown": 4, "swarm_manager_nodes_total": 10, "swarm_manager_secrets_total": 1, "swarm_manager_services_total": 1, "swarm_manager_tasks_state_accepted": 1, "swarm_manager_tasks_state_assigned": 2, "swarm_manager_tasks_state_complete": 3, "swarm_manager_tasks_state_failed": 4, "swarm_manager_tasks_state_new": 5, "swarm_manager_tasks_state_orphaned": 6, "swarm_manager_tasks_state_pending": 7, "swarm_manager_tasks_state_preparing": 8, "swarm_manager_tasks_state_ready": 9, "swarm_manager_tasks_state_rejected": 10, "swarm_manager_tasks_state_remove": 11, "swarm_manager_tasks_state_running": 12, "swarm_manager_tasks_state_shutdown": 13, "swarm_manager_tasks_state_starting": 14, "swarm_manager_tasks_total": 105, }, }, } for name, test := range tests { t.Run(name, func(t *testing.T) { pulsar, srv := test.prepare(t) defer srv.Close() for i := 0; i < 10; i++ { _ = pulsar.Collect() } collected := pulsar.Collect() require.NotNil(t, collected) require.Equal(t, test.expected, collected) ensureCollectedHasAllChartsDimsVarsIDs(t, pulsar, collected) }) } } func ensureCollectedHasAllChartsDimsVarsIDs(t *testing.T, dockerEngine *DockerEngine, collected map[string]int64) { t.Helper() for _, chart := range *dockerEngine.Charts() { for _, dim := range chart.Dims { _, ok := collected[dim.ID] assert.Truef(t, ok, "collected metrics has no data for dim '%s' chart '%s'", dim.ID, chart.ID) } for _, v := range chart.Vars { _, ok := collected[v.ID] assert.Truef(t, ok, "collected metrics has no data for var '%s' chart '%s'", v.ID, chart.ID) } } } func prepareClientServerV17050CE(t *testing.T) (*DockerEngine, *httptest.Server) { t.Helper() srv := httptest.NewServer(http.HandlerFunc( func(w http.ResponseWriter, r *http.Request) { _, _ = w.Write(dataVer17050Metrics) })) dockerEngine := New() dockerEngine.URL = srv.URL require.NoError(t, dockerEngine.Init()) return dockerEngine, srv } func prepareClientServerV18093CE(t *testing.T) (*DockerEngine, *httptest.Server) { t.Helper() srv := httptest.NewServer(http.HandlerFunc( func(w http.ResponseWriter, r *http.Request) { _, _ = w.Write(dataVer18093Metrics) })) dockerEngine := New() dockerEngine.URL = srv.URL require.NoError(t, dockerEngine.Init()) return dockerEngine, srv } func prepareClientServerV18093CESwarm(t *testing.T) (*DockerEngine, *httptest.Server) { t.Helper() srv := httptest.NewServer(http.HandlerFunc( func(w http.ResponseWriter, r *http.Request) { _, _ = w.Write(dataVer18093SwarmMetrics) })) dockerEngine := New() dockerEngine.URL = srv.URL require.NoError(t, dockerEngine.Init()) return dockerEngine, srv } func prepareClientServerNonDockerEngine(t *testing.T) (*DockerEngine, *httptest.Server) { t.Helper() srv := httptest.NewServer(http.HandlerFunc( func(w http.ResponseWriter, r *http.Request) { _, _ = w.Write(dataNonDockerEngineMetrics) })) dockerEngine := New() dockerEngine.URL = srv.URL require.NoError(t, dockerEngine.Init()) return dockerEngine, srv } func prepareClientServerInvalidData(t *testing.T) (*DockerEngine, *httptest.Server) { t.Helper() srv := httptest.NewServer(http.HandlerFunc( func(w http.ResponseWriter, r *http.Request) { _, _ = w.Write([]byte("hello and\n goodbye")) })) dockerEngine := New() dockerEngine.URL = srv.URL require.NoError(t, dockerEngine.Init()) return dockerEngine, srv } func prepareClientServer404(t *testing.T) (*DockerEngine, *httptest.Server) { t.Helper() srv := httptest.NewServer(http.HandlerFunc( func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotFound) })) dockerEngine := New() dockerEngine.URL = srv.URL require.NoError(t, dockerEngine.Init()) return dockerEngine, srv } func prepareClientServerConnectionRefused(t *testing.T) (*DockerEngine, *httptest.Server) { t.Helper() srv := httptest.NewServer(nil) dockerEngine := New() dockerEngine.URL = "http://127.0.0.1:38001/metrics" require.NoError(t, dockerEngine.Init()) return dockerEngine, srv }