github.com/netdata/go.d.plugin@v0.58.1/modules/docker_engine/docker_engine_test.go (about)

     1  // SPDX-License-Identifier: GPL-3.0-or-later
     2  
     3  package docker_engine
     4  
     5  import (
     6  	"net/http"
     7  	"net/http/httptest"
     8  	"os"
     9  	"testing"
    10  
    11  	"github.com/netdata/go.d.plugin/pkg/tlscfg"
    12  	"github.com/netdata/go.d.plugin/pkg/web"
    13  
    14  	"github.com/netdata/go.d.plugin/agent/module"
    15  	"github.com/stretchr/testify/assert"
    16  	"github.com/stretchr/testify/require"
    17  )
    18  
    19  var (
    20  	metricsNonDockerEngine, _ = os.ReadFile("testdata/non-docker-engine.txt")
    21  	metricsV17050CE, _        = os.ReadFile("testdata/v17.05.0-ce.txt")
    22  	metricsV18093CE, _        = os.ReadFile("testdata/v18.09.3-ce.txt")
    23  	metricsV18093CESwarm, _   = os.ReadFile("testdata/v18.09.3-ce-swarm.txt")
    24  )
    25  
    26  func Test_readTestData(t *testing.T) {
    27  	assert.NotNil(t, metricsNonDockerEngine)
    28  	assert.NotNil(t, metricsV17050CE)
    29  	assert.NotNil(t, metricsV18093CE)
    30  	assert.NotNil(t, metricsV18093CESwarm)
    31  }
    32  
    33  func TestNew(t *testing.T) {
    34  	assert.Implements(t, (*module.Module)(nil), New())
    35  }
    36  
    37  func TestDockerEngine_Cleanup(t *testing.T) {
    38  	assert.NotPanics(t, New().Cleanup)
    39  }
    40  
    41  func TestDockerEngine_Init(t *testing.T) {
    42  	tests := map[string]struct {
    43  		config   Config
    44  		wantFail bool
    45  	}{
    46  		"default": {
    47  			config: New().Config,
    48  		},
    49  		"empty URL": {
    50  			config:   Config{HTTP: web.HTTP{Request: web.Request{URL: ""}}},
    51  			wantFail: true,
    52  		},
    53  		"nonexistent TLS CA": {
    54  			config: Config{HTTP: web.HTTP{
    55  				Request: web.Request{URL: "http://127.0.0.1:9323/metrics"},
    56  				Client:  web.Client{TLSConfig: tlscfg.TLSConfig{TLSCA: "testdata/tls"}}}},
    57  			wantFail: true,
    58  		},
    59  	}
    60  
    61  	for name, test := range tests {
    62  		t.Run(name, func(t *testing.T) {
    63  			dockerEngine := New()
    64  			dockerEngine.Config = test.config
    65  
    66  			if test.wantFail {
    67  				assert.False(t, dockerEngine.Init())
    68  			} else {
    69  				assert.True(t, dockerEngine.Init())
    70  			}
    71  		})
    72  	}
    73  }
    74  
    75  func TestDockerEngine_Check(t *testing.T) {
    76  	tests := map[string]struct {
    77  		prepare  func(*testing.T) (*DockerEngine, *httptest.Server)
    78  		wantFail bool
    79  	}{
    80  		"v17.05.0-ce":        {prepare: prepareClientServerV17050CE},
    81  		"v18.09.3-ce":        {prepare: prepareClientServerV18093CE},
    82  		"v18.09.3-ce-swarm":  {prepare: prepareClientServerV18093CESwarm},
    83  		"non docker engine":  {prepare: prepareClientServerNonDockerEngine, wantFail: true},
    84  		"invalid data":       {prepare: prepareClientServerInvalidData, wantFail: true},
    85  		"404":                {prepare: prepareClientServer404, wantFail: true},
    86  		"connection refused": {prepare: prepareClientServerConnectionRefused, wantFail: true},
    87  	}
    88  
    89  	for name, test := range tests {
    90  		t.Run(name, func(t *testing.T) {
    91  			dockerEngine, srv := test.prepare(t)
    92  			defer srv.Close()
    93  
    94  			if test.wantFail {
    95  				assert.False(t, dockerEngine.Check())
    96  			} else {
    97  				assert.True(t, dockerEngine.Check())
    98  			}
    99  		})
   100  	}
   101  }
   102  
   103  func TestDockerEngine_Charts(t *testing.T) {
   104  	tests := map[string]struct {
   105  		prepare       func(*testing.T) (*DockerEngine, *httptest.Server)
   106  		wantNumCharts int
   107  	}{
   108  		"v17.05.0-ce":       {prepare: prepareClientServerV17050CE, wantNumCharts: len(charts) - 1}, // no container states chart
   109  		"v18.09.3-ce":       {prepare: prepareClientServerV18093CE, wantNumCharts: len(charts)},
   110  		"v18.09.3-ce-swarm": {prepare: prepareClientServerV18093CESwarm, wantNumCharts: len(charts) + len(swarmManagerCharts)},
   111  	}
   112  
   113  	for name, test := range tests {
   114  		t.Run(name, func(t *testing.T) {
   115  			dockerEngine, srv := test.prepare(t)
   116  			defer srv.Close()
   117  
   118  			require.True(t, dockerEngine.Check())
   119  			assert.Len(t, *dockerEngine.Charts(), test.wantNumCharts)
   120  		})
   121  	}
   122  }
   123  
   124  func TestDockerEngine_Collect_ReturnsNilOnErrors(t *testing.T) {
   125  	tests := map[string]struct {
   126  		prepare func(*testing.T) (*DockerEngine, *httptest.Server)
   127  	}{
   128  		"non docker engine":  {prepare: prepareClientServerNonDockerEngine},
   129  		"invalid data":       {prepare: prepareClientServerInvalidData},
   130  		"404":                {prepare: prepareClientServer404},
   131  		"connection refused": {prepare: prepareClientServerConnectionRefused},
   132  	}
   133  
   134  	for name, test := range tests {
   135  		t.Run(name, func(t *testing.T) {
   136  			dockerEngine, srv := test.prepare(t)
   137  			defer srv.Close()
   138  
   139  			assert.Nil(t, dockerEngine.Collect())
   140  		})
   141  	}
   142  }
   143  
   144  func TestDockerEngine_Collect(t *testing.T) {
   145  	tests := map[string]struct {
   146  		prepare  func(*testing.T) (*DockerEngine, *httptest.Server)
   147  		expected map[string]int64
   148  	}{
   149  		"v17.05.0-ce": {
   150  			prepare: prepareClientServerV17050CE,
   151  			expected: map[string]int64{
   152  				"builder_fails_build_canceled":                   1,
   153  				"builder_fails_build_target_not_reachable_error": 2,
   154  				"builder_fails_command_not_supported_error":      3,
   155  				"builder_fails_dockerfile_empty_error":           4,
   156  				"builder_fails_dockerfile_syntax_error":          5,
   157  				"builder_fails_error_processing_commands_error":  6,
   158  				"builder_fails_missing_onbuild_arguments_error":  7,
   159  				"builder_fails_unknown_instruction_error":        8,
   160  				"container_actions_changes":                      1,
   161  				"container_actions_commit":                       1,
   162  				"container_actions_create":                       1,
   163  				"container_actions_delete":                       1,
   164  				"container_actions_start":                        1,
   165  				"health_checks_failed":                           33,
   166  			},
   167  		},
   168  		"v18.09.3-ce": {
   169  			prepare: prepareClientServerV18093CE,
   170  			expected: map[string]int64{
   171  				"builder_fails_build_canceled":                   1,
   172  				"builder_fails_build_target_not_reachable_error": 2,
   173  				"builder_fails_command_not_supported_error":      3,
   174  				"builder_fails_dockerfile_empty_error":           4,
   175  				"builder_fails_dockerfile_syntax_error":          5,
   176  				"builder_fails_error_processing_commands_error":  6,
   177  				"builder_fails_missing_onbuild_arguments_error":  7,
   178  				"builder_fails_unknown_instruction_error":        8,
   179  				"container_actions_changes":                      1,
   180  				"container_actions_commit":                       1,
   181  				"container_actions_create":                       1,
   182  				"container_actions_delete":                       1,
   183  				"container_actions_start":                        1,
   184  				"container_states_paused":                        11,
   185  				"container_states_running":                       12,
   186  				"container_states_stopped":                       13,
   187  				"health_checks_failed":                           33,
   188  			},
   189  		},
   190  		"v18.09.3-ce-swarm": {
   191  			prepare: prepareClientServerV18093CESwarm,
   192  			expected: map[string]int64{
   193  				"builder_fails_build_canceled":                   1,
   194  				"builder_fails_build_target_not_reachable_error": 2,
   195  				"builder_fails_command_not_supported_error":      3,
   196  				"builder_fails_dockerfile_empty_error":           4,
   197  				"builder_fails_dockerfile_syntax_error":          5,
   198  				"builder_fails_error_processing_commands_error":  6,
   199  				"builder_fails_missing_onbuild_arguments_error":  7,
   200  				"builder_fails_unknown_instruction_error":        8,
   201  				"container_actions_changes":                      1,
   202  				"container_actions_commit":                       1,
   203  				"container_actions_create":                       1,
   204  				"container_actions_delete":                       1,
   205  				"container_actions_start":                        1,
   206  				"container_states_paused":                        11,
   207  				"container_states_running":                       12,
   208  				"container_states_stopped":                       13,
   209  				"health_checks_failed":                           33,
   210  				"swarm_manager_configs_total":                    1,
   211  				"swarm_manager_leader":                           1,
   212  				"swarm_manager_networks_total":                   3,
   213  				"swarm_manager_nodes_state_disconnected":         1,
   214  				"swarm_manager_nodes_state_down":                 2,
   215  				"swarm_manager_nodes_state_ready":                3,
   216  				"swarm_manager_nodes_state_unknown":              4,
   217  				"swarm_manager_nodes_total":                      10,
   218  				"swarm_manager_secrets_total":                    1,
   219  				"swarm_manager_services_total":                   1,
   220  				"swarm_manager_tasks_state_accepted":             1,
   221  				"swarm_manager_tasks_state_assigned":             2,
   222  				"swarm_manager_tasks_state_complete":             3,
   223  				"swarm_manager_tasks_state_failed":               4,
   224  				"swarm_manager_tasks_state_new":                  5,
   225  				"swarm_manager_tasks_state_orphaned":             6,
   226  				"swarm_manager_tasks_state_pending":              7,
   227  				"swarm_manager_tasks_state_preparing":            8,
   228  				"swarm_manager_tasks_state_ready":                9,
   229  				"swarm_manager_tasks_state_rejected":             10,
   230  				"swarm_manager_tasks_state_remove":               11,
   231  				"swarm_manager_tasks_state_running":              12,
   232  				"swarm_manager_tasks_state_shutdown":             13,
   233  				"swarm_manager_tasks_state_starting":             14,
   234  				"swarm_manager_tasks_total":                      105,
   235  			},
   236  		},
   237  	}
   238  
   239  	for name, test := range tests {
   240  		t.Run(name, func(t *testing.T) {
   241  			pulsar, srv := test.prepare(t)
   242  			defer srv.Close()
   243  
   244  			for i := 0; i < 10; i++ {
   245  				_ = pulsar.Collect()
   246  			}
   247  			collected := pulsar.Collect()
   248  
   249  			require.NotNil(t, collected)
   250  			require.Equal(t, test.expected, collected)
   251  			ensureCollectedHasAllChartsDimsVarsIDs(t, pulsar, collected)
   252  		})
   253  	}
   254  }
   255  
   256  func ensureCollectedHasAllChartsDimsVarsIDs(t *testing.T, dockerEngine *DockerEngine, collected map[string]int64) {
   257  	t.Helper()
   258  	for _, chart := range *dockerEngine.Charts() {
   259  		for _, dim := range chart.Dims {
   260  			_, ok := collected[dim.ID]
   261  			assert.Truef(t, ok, "collected metrics has no data for dim '%s' chart '%s'", dim.ID, chart.ID)
   262  		}
   263  		for _, v := range chart.Vars {
   264  			_, ok := collected[v.ID]
   265  			assert.Truef(t, ok, "collected metrics has no data for var '%s' chart '%s'", v.ID, chart.ID)
   266  		}
   267  	}
   268  }
   269  
   270  func prepareClientServerV17050CE(t *testing.T) (*DockerEngine, *httptest.Server) {
   271  	t.Helper()
   272  	srv := httptest.NewServer(http.HandlerFunc(
   273  		func(w http.ResponseWriter, r *http.Request) {
   274  			_, _ = w.Write(metricsV17050CE)
   275  		}))
   276  
   277  	dockerEngine := New()
   278  	dockerEngine.URL = srv.URL
   279  	require.True(t, dockerEngine.Init())
   280  
   281  	return dockerEngine, srv
   282  }
   283  
   284  func prepareClientServerV18093CE(t *testing.T) (*DockerEngine, *httptest.Server) {
   285  	t.Helper()
   286  	srv := httptest.NewServer(http.HandlerFunc(
   287  		func(w http.ResponseWriter, r *http.Request) {
   288  			_, _ = w.Write(metricsV18093CE)
   289  		}))
   290  
   291  	dockerEngine := New()
   292  	dockerEngine.URL = srv.URL
   293  	require.True(t, dockerEngine.Init())
   294  
   295  	return dockerEngine, srv
   296  }
   297  
   298  func prepareClientServerV18093CESwarm(t *testing.T) (*DockerEngine, *httptest.Server) {
   299  	t.Helper()
   300  	srv := httptest.NewServer(http.HandlerFunc(
   301  		func(w http.ResponseWriter, r *http.Request) {
   302  			_, _ = w.Write(metricsV18093CESwarm)
   303  		}))
   304  
   305  	dockerEngine := New()
   306  	dockerEngine.URL = srv.URL
   307  	require.True(t, dockerEngine.Init())
   308  
   309  	return dockerEngine, srv
   310  }
   311  
   312  func prepareClientServerNonDockerEngine(t *testing.T) (*DockerEngine, *httptest.Server) {
   313  	t.Helper()
   314  	srv := httptest.NewServer(http.HandlerFunc(
   315  		func(w http.ResponseWriter, r *http.Request) {
   316  			_, _ = w.Write(metricsNonDockerEngine)
   317  		}))
   318  
   319  	dockerEngine := New()
   320  	dockerEngine.URL = srv.URL
   321  	require.True(t, dockerEngine.Init())
   322  
   323  	return dockerEngine, srv
   324  }
   325  
   326  func prepareClientServerInvalidData(t *testing.T) (*DockerEngine, *httptest.Server) {
   327  	t.Helper()
   328  	srv := httptest.NewServer(http.HandlerFunc(
   329  		func(w http.ResponseWriter, r *http.Request) {
   330  			_, _ = w.Write([]byte("hello and\n goodbye"))
   331  		}))
   332  
   333  	dockerEngine := New()
   334  	dockerEngine.URL = srv.URL
   335  	require.True(t, dockerEngine.Init())
   336  
   337  	return dockerEngine, srv
   338  }
   339  
   340  func prepareClientServer404(t *testing.T) (*DockerEngine, *httptest.Server) {
   341  	t.Helper()
   342  	srv := httptest.NewServer(http.HandlerFunc(
   343  		func(w http.ResponseWriter, r *http.Request) {
   344  			w.WriteHeader(http.StatusNotFound)
   345  		}))
   346  
   347  	dockerEngine := New()
   348  	dockerEngine.URL = srv.URL
   349  	require.True(t, dockerEngine.Init())
   350  
   351  	return dockerEngine, srv
   352  }
   353  
   354  func prepareClientServerConnectionRefused(t *testing.T) (*DockerEngine, *httptest.Server) {
   355  	t.Helper()
   356  	srv := httptest.NewServer(nil)
   357  
   358  	dockerEngine := New()
   359  	dockerEngine.URL = "http://127.0.0.1:38001/metrics"
   360  	require.True(t, dockerEngine.Init())
   361  
   362  	return dockerEngine, srv
   363  }