github.com/netdata/go.d.plugin@v0.58.1/modules/k8s_state/kube_state_test.go (about)

     1  // SPDX-License-Identifier: GPL-3.0-or-later
     2  
     3  package k8s_state
     4  
     5  import (
     6  	"context"
     7  	"errors"
     8  	"fmt"
     9  	"strings"
    10  	"testing"
    11  	"time"
    12  
    13  	"github.com/netdata/go.d.plugin/agent/module"
    14  
    15  	"github.com/stretchr/testify/assert"
    16  	"github.com/stretchr/testify/require"
    17  	corev1 "k8s.io/api/core/v1"
    18  	apiresource "k8s.io/apimachinery/pkg/api/resource"
    19  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    20  	"k8s.io/apimachinery/pkg/version"
    21  	"k8s.io/client-go/discovery"
    22  	"k8s.io/client-go/kubernetes"
    23  	"k8s.io/client-go/kubernetes/fake"
    24  )
    25  
    26  func TestNew(t *testing.T) {
    27  	assert.Implements(t, (*module.Module)(nil), New())
    28  }
    29  
    30  func TestKubeState_Init(t *testing.T) {
    31  	tests := map[string]struct {
    32  		wantFail bool
    33  		prepare  func() *KubeState
    34  	}{
    35  		"success when no error on initializing K8s client": {
    36  			wantFail: false,
    37  			prepare: func() *KubeState {
    38  				ks := New()
    39  				ks.newKubeClient = func() (kubernetes.Interface, error) { return fake.NewSimpleClientset(), nil }
    40  				return ks
    41  			},
    42  		},
    43  		"fail when get an error on initializing K8s client": {
    44  			wantFail: true,
    45  			prepare: func() *KubeState {
    46  				ks := New()
    47  				ks.newKubeClient = func() (kubernetes.Interface, error) { return nil, errors.New("newKubeClient() error") }
    48  				return ks
    49  			},
    50  		},
    51  	}
    52  
    53  	for name, test := range tests {
    54  		t.Run(name, func(t *testing.T) {
    55  			ks := test.prepare()
    56  
    57  			if test.wantFail {
    58  				assert.False(t, ks.Init())
    59  			} else {
    60  				assert.True(t, ks.Init())
    61  			}
    62  		})
    63  	}
    64  }
    65  
    66  func TestKubeState_Check(t *testing.T) {
    67  	tests := map[string]struct {
    68  		wantFail bool
    69  		prepare  func() *KubeState
    70  	}{
    71  		"success when connected to the K8s API": {
    72  			wantFail: false,
    73  			prepare: func() *KubeState {
    74  				ks := New()
    75  				ks.newKubeClient = func() (kubernetes.Interface, error) { return fake.NewSimpleClientset(), nil }
    76  				return ks
    77  			},
    78  		},
    79  		"fail when not connected to the K8s API": {
    80  			wantFail: true,
    81  			prepare: func() *KubeState {
    82  				ks := New()
    83  				client := &brokenInfoKubeClient{fake.NewSimpleClientset()}
    84  				ks.newKubeClient = func() (kubernetes.Interface, error) { return client, nil }
    85  				return ks
    86  			},
    87  		},
    88  	}
    89  
    90  	for name, test := range tests {
    91  		t.Run(name, func(t *testing.T) {
    92  			ks := test.prepare()
    93  			require.True(t, ks.Init())
    94  
    95  			if test.wantFail {
    96  				assert.False(t, ks.Check())
    97  			} else {
    98  				assert.True(t, ks.Check())
    99  			}
   100  		})
   101  	}
   102  }
   103  
   104  func TestKubeState_Charts(t *testing.T) {
   105  	ks := New()
   106  
   107  	assert.NotEmpty(t, *ks.Charts())
   108  }
   109  
   110  func TestKubeState_Cleanup(t *testing.T) {
   111  	tests := map[string]struct {
   112  		prepare   func() *KubeState
   113  		doInit    bool
   114  		doCollect bool
   115  	}{
   116  		"before init": {
   117  			doInit:    false,
   118  			doCollect: false,
   119  			prepare: func() *KubeState {
   120  				ks := New()
   121  				ks.newKubeClient = func() (kubernetes.Interface, error) { return fake.NewSimpleClientset(), nil }
   122  				return ks
   123  			},
   124  		},
   125  		"after init": {
   126  			doInit:    true,
   127  			doCollect: false,
   128  			prepare: func() *KubeState {
   129  				ks := New()
   130  				ks.newKubeClient = func() (kubernetes.Interface, error) { return fake.NewSimpleClientset(), nil }
   131  				return ks
   132  			},
   133  		},
   134  		"after collect": {
   135  			doInit:    true,
   136  			doCollect: true,
   137  			prepare: func() *KubeState {
   138  				ks := New()
   139  				ks.newKubeClient = func() (kubernetes.Interface, error) { return fake.NewSimpleClientset(), nil }
   140  				return ks
   141  			},
   142  		},
   143  	}
   144  
   145  	for name, test := range tests {
   146  		t.Run(name, func(t *testing.T) {
   147  			ks := test.prepare()
   148  
   149  			if test.doInit {
   150  				_ = ks.Init()
   151  			}
   152  			if test.doCollect {
   153  				_ = ks.Collect()
   154  				time.Sleep(ks.initDelay)
   155  			}
   156  
   157  			assert.NotPanics(t, ks.Cleanup)
   158  			time.Sleep(time.Second)
   159  			if test.doCollect {
   160  				assert.True(t, ks.discoverer.stopped())
   161  			}
   162  		})
   163  	}
   164  }
   165  
   166  func TestKubeState_Collect(t *testing.T) {
   167  	type (
   168  		testCaseStep func(t *testing.T, ks *KubeState)
   169  		testCase     struct {
   170  			client kubernetes.Interface
   171  			steps  []testCaseStep
   172  		}
   173  	)
   174  
   175  	tests := map[string]struct {
   176  		create func(t *testing.T) testCase
   177  	}{
   178  		"Node only": {
   179  			create: func(t *testing.T) testCase {
   180  				client := fake.NewSimpleClientset(
   181  					newNode("node01"),
   182  				)
   183  
   184  				step1 := func(t *testing.T, ks *KubeState) {
   185  					mx := ks.Collect()
   186  					expected := map[string]int64{
   187  						"discovery_node_discoverer_state":              1,
   188  						"discovery_pod_discoverer_state":               1,
   189  						"node_node01_age":                              3,
   190  						"node_node01_alloc_cpu_limits_used":            0,
   191  						"node_node01_alloc_cpu_limits_util":            0,
   192  						"node_node01_alloc_cpu_requests_used":          0,
   193  						"node_node01_alloc_cpu_requests_util":          0,
   194  						"node_node01_alloc_mem_limits_used":            0,
   195  						"node_node01_alloc_mem_limits_util":            0,
   196  						"node_node01_alloc_mem_requests_used":          0,
   197  						"node_node01_alloc_mem_requests_util":          0,
   198  						"node_node01_alloc_pods_allocated":             0,
   199  						"node_node01_alloc_pods_available":             110,
   200  						"node_node01_alloc_pods_util":                  0,
   201  						"node_node01_cond_diskpressure":                0,
   202  						"node_node01_cond_memorypressure":              0,
   203  						"node_node01_cond_networkunavailable":          0,
   204  						"node_node01_cond_pidpressure":                 0,
   205  						"node_node01_cond_ready":                       1,
   206  						"node_node01_schedulability_schedulable":       1,
   207  						"node_node01_schedulability_unschedulable":     0,
   208  						"node_node01_containers":                       0,
   209  						"node_node01_containers_state_running":         0,
   210  						"node_node01_containers_state_terminated":      0,
   211  						"node_node01_containers_state_waiting":         0,
   212  						"node_node01_init_containers":                  0,
   213  						"node_node01_init_containers_state_running":    0,
   214  						"node_node01_init_containers_state_terminated": 0,
   215  						"node_node01_init_containers_state_waiting":    0,
   216  						"node_node01_pods_cond_containersready":        0,
   217  						"node_node01_pods_cond_podinitialized":         0,
   218  						"node_node01_pods_cond_podready":               0,
   219  						"node_node01_pods_cond_podscheduled":           0,
   220  						"node_node01_pods_phase_failed":                0,
   221  						"node_node01_pods_phase_pending":               0,
   222  						"node_node01_pods_phase_running":               0,
   223  						"node_node01_pods_phase_succeeded":             0,
   224  						"node_node01_pods_readiness":                   0,
   225  						"node_node01_pods_readiness_ready":             0,
   226  						"node_node01_pods_readiness_unready":           0,
   227  					}
   228  					copyAge(expected, mx)
   229  					assert.Equal(t, expected, mx)
   230  					assert.Equal(t,
   231  						len(nodeChartsTmpl)+len(baseCharts),
   232  						len(*ks.Charts()),
   233  					)
   234  				}
   235  
   236  				return testCase{
   237  					client: client,
   238  					steps:  []testCaseStep{step1},
   239  				}
   240  			},
   241  		},
   242  		"Pod only": {
   243  			create: func(t *testing.T) testCase {
   244  				pod := newPod("node01", "pod01")
   245  				client := fake.NewSimpleClientset(
   246  					pod,
   247  				)
   248  
   249  				step1 := func(t *testing.T, ks *KubeState) {
   250  					mx := ks.Collect()
   251  					expected := map[string]int64{
   252  						"discovery_node_discoverer_state":                         1,
   253  						"discovery_pod_discoverer_state":                          1,
   254  						"pod_default_pod01_age":                                   3,
   255  						"pod_default_pod01_cpu_limits_used":                       400,
   256  						"pod_default_pod01_cpu_requests_used":                     200,
   257  						"pod_default_pod01_mem_limits_used":                       419430400,
   258  						"pod_default_pod01_mem_requests_used":                     209715200,
   259  						"pod_default_pod01_cond_containersready":                  1,
   260  						"pod_default_pod01_cond_podinitialized":                   1,
   261  						"pod_default_pod01_cond_podready":                         1,
   262  						"pod_default_pod01_cond_podscheduled":                     1,
   263  						"pod_default_pod01_container_container1_readiness":        1,
   264  						"pod_default_pod01_container_container1_restarts":         0,
   265  						"pod_default_pod01_container_container1_state_running":    1,
   266  						"pod_default_pod01_container_container1_state_terminated": 0,
   267  						"pod_default_pod01_container_container1_state_waiting":    0,
   268  						"pod_default_pod01_container_container2_readiness":        1,
   269  						"pod_default_pod01_container_container2_restarts":         0,
   270  						"pod_default_pod01_container_container2_state_running":    1,
   271  						"pod_default_pod01_container_container2_state_terminated": 0,
   272  						"pod_default_pod01_container_container2_state_waiting":    0,
   273  						"pod_default_pod01_containers":                            2,
   274  						"pod_default_pod01_containers_state_running":              2,
   275  						"pod_default_pod01_containers_state_terminated":           0,
   276  						"pod_default_pod01_containers_state_waiting":              0,
   277  						"pod_default_pod01_init_containers":                       1,
   278  						"pod_default_pod01_init_containers_state_running":         0,
   279  						"pod_default_pod01_init_containers_state_terminated":      1,
   280  						"pod_default_pod01_init_containers_state_waiting":         0,
   281  						"pod_default_pod01_phase_failed":                          0,
   282  						"pod_default_pod01_phase_pending":                         0,
   283  						"pod_default_pod01_phase_running":                         1,
   284  						"pod_default_pod01_phase_succeeded":                       0,
   285  					}
   286  					copyAge(expected, mx)
   287  
   288  					assert.Equal(t, expected, mx)
   289  					assert.Equal(t,
   290  						len(podChartsTmpl)+len(containerChartsTmpl)*len(pod.Spec.Containers)+len(baseCharts),
   291  						len(*ks.Charts()),
   292  					)
   293  				}
   294  
   295  				return testCase{
   296  					client: client,
   297  					steps:  []testCaseStep{step1},
   298  				}
   299  			},
   300  		},
   301  		"Nodes and Pods": {
   302  			create: func(t *testing.T) testCase {
   303  				node := newNode("node01")
   304  				pod := newPod(node.Name, "pod01")
   305  				client := fake.NewSimpleClientset(
   306  					node,
   307  					pod,
   308  				)
   309  
   310  				step1 := func(t *testing.T, ks *KubeState) {
   311  					mx := ks.Collect()
   312  					expected := map[string]int64{
   313  						"discovery_node_discoverer_state":                         1,
   314  						"discovery_pod_discoverer_state":                          1,
   315  						"node_node01_age":                                         3,
   316  						"node_node01_alloc_cpu_limits_used":                       400,
   317  						"node_node01_alloc_cpu_limits_util":                       11428,
   318  						"node_node01_alloc_cpu_requests_used":                     200,
   319  						"node_node01_alloc_cpu_requests_util":                     5714,
   320  						"node_node01_alloc_mem_limits_used":                       419430400,
   321  						"node_node01_alloc_mem_limits_util":                       11428,
   322  						"node_node01_alloc_mem_requests_used":                     209715200,
   323  						"node_node01_alloc_mem_requests_util":                     5714,
   324  						"node_node01_alloc_pods_allocated":                        1,
   325  						"node_node01_alloc_pods_available":                        109,
   326  						"node_node01_alloc_pods_util":                             909,
   327  						"node_node01_cond_diskpressure":                           0,
   328  						"node_node01_cond_memorypressure":                         0,
   329  						"node_node01_cond_networkunavailable":                     0,
   330  						"node_node01_cond_pidpressure":                            0,
   331  						"node_node01_cond_ready":                                  1,
   332  						"node_node01_schedulability_schedulable":                  1,
   333  						"node_node01_schedulability_unschedulable":                0,
   334  						"node_node01_containers":                                  2,
   335  						"node_node01_containers_state_running":                    2,
   336  						"node_node01_containers_state_terminated":                 0,
   337  						"node_node01_containers_state_waiting":                    0,
   338  						"node_node01_init_containers":                             1,
   339  						"node_node01_init_containers_state_running":               0,
   340  						"node_node01_init_containers_state_terminated":            1,
   341  						"node_node01_init_containers_state_waiting":               0,
   342  						"node_node01_pods_cond_containersready":                   1,
   343  						"node_node01_pods_cond_podinitialized":                    1,
   344  						"node_node01_pods_cond_podready":                          1,
   345  						"node_node01_pods_cond_podscheduled":                      1,
   346  						"node_node01_pods_phase_failed":                           0,
   347  						"node_node01_pods_phase_pending":                          0,
   348  						"node_node01_pods_phase_running":                          1,
   349  						"node_node01_pods_phase_succeeded":                        0,
   350  						"node_node01_pods_readiness":                              100000,
   351  						"node_node01_pods_readiness_ready":                        1,
   352  						"node_node01_pods_readiness_unready":                      0,
   353  						"pod_default_pod01_age":                                   3,
   354  						"pod_default_pod01_cpu_limits_used":                       400,
   355  						"pod_default_pod01_cpu_requests_used":                     200,
   356  						"pod_default_pod01_mem_limits_used":                       419430400,
   357  						"pod_default_pod01_mem_requests_used":                     209715200,
   358  						"pod_default_pod01_cond_containersready":                  1,
   359  						"pod_default_pod01_cond_podinitialized":                   1,
   360  						"pod_default_pod01_cond_podready":                         1,
   361  						"pod_default_pod01_cond_podscheduled":                     1,
   362  						"pod_default_pod01_container_container1_readiness":        1,
   363  						"pod_default_pod01_container_container1_restarts":         0,
   364  						"pod_default_pod01_container_container1_state_running":    1,
   365  						"pod_default_pod01_container_container1_state_terminated": 0,
   366  						"pod_default_pod01_container_container1_state_waiting":    0,
   367  						"pod_default_pod01_container_container2_readiness":        1,
   368  						"pod_default_pod01_container_container2_restarts":         0,
   369  						"pod_default_pod01_container_container2_state_running":    1,
   370  						"pod_default_pod01_container_container2_state_terminated": 0,
   371  						"pod_default_pod01_container_container2_state_waiting":    0,
   372  						"pod_default_pod01_containers":                            2,
   373  						"pod_default_pod01_containers_state_running":              2,
   374  						"pod_default_pod01_containers_state_terminated":           0,
   375  						"pod_default_pod01_containers_state_waiting":              0,
   376  						"pod_default_pod01_init_containers":                       1,
   377  						"pod_default_pod01_init_containers_state_running":         0,
   378  						"pod_default_pod01_init_containers_state_terminated":      1,
   379  						"pod_default_pod01_init_containers_state_waiting":         0,
   380  						"pod_default_pod01_phase_failed":                          0,
   381  						"pod_default_pod01_phase_pending":                         0,
   382  						"pod_default_pod01_phase_running":                         1,
   383  						"pod_default_pod01_phase_succeeded":                       0,
   384  					}
   385  					copyAge(expected, mx)
   386  
   387  					assert.Equal(t, expected, mx)
   388  					assert.Equal(t,
   389  						len(nodeChartsTmpl)+len(podChartsTmpl)+len(containerChartsTmpl)*len(pod.Spec.Containers)+len(baseCharts),
   390  						len(*ks.Charts()),
   391  					)
   392  				}
   393  
   394  				return testCase{
   395  					client: client,
   396  					steps:  []testCaseStep{step1},
   397  				}
   398  			},
   399  		},
   400  		"delete a Pod in runtime": {
   401  			create: func(t *testing.T) testCase {
   402  				ctx := context.Background()
   403  				node := newNode("node01")
   404  				pod := newPod(node.Name, "pod01")
   405  				client := fake.NewSimpleClientset(
   406  					node,
   407  					pod,
   408  				)
   409  				step1 := func(t *testing.T, ks *KubeState) {
   410  					_ = ks.Collect()
   411  					_ = client.CoreV1().Pods(pod.Namespace).Delete(ctx, pod.Name, metav1.DeleteOptions{})
   412  				}
   413  
   414  				step2 := func(t *testing.T, ks *KubeState) {
   415  					mx := ks.Collect()
   416  					expected := map[string]int64{
   417  						"discovery_node_discoverer_state":              1,
   418  						"discovery_pod_discoverer_state":               1,
   419  						"node_node01_age":                              4,
   420  						"node_node01_alloc_cpu_limits_used":            0,
   421  						"node_node01_alloc_cpu_limits_util":            0,
   422  						"node_node01_alloc_cpu_requests_used":          0,
   423  						"node_node01_alloc_cpu_requests_util":          0,
   424  						"node_node01_alloc_mem_limits_used":            0,
   425  						"node_node01_alloc_mem_limits_util":            0,
   426  						"node_node01_alloc_mem_requests_used":          0,
   427  						"node_node01_alloc_mem_requests_util":          0,
   428  						"node_node01_alloc_pods_allocated":             0,
   429  						"node_node01_alloc_pods_available":             110,
   430  						"node_node01_alloc_pods_util":                  0,
   431  						"node_node01_cond_diskpressure":                0,
   432  						"node_node01_cond_memorypressure":              0,
   433  						"node_node01_cond_networkunavailable":          0,
   434  						"node_node01_cond_pidpressure":                 0,
   435  						"node_node01_cond_ready":                       1,
   436  						"node_node01_schedulability_schedulable":       1,
   437  						"node_node01_schedulability_unschedulable":     0,
   438  						"node_node01_containers":                       0,
   439  						"node_node01_containers_state_running":         0,
   440  						"node_node01_containers_state_terminated":      0,
   441  						"node_node01_containers_state_waiting":         0,
   442  						"node_node01_init_containers":                  0,
   443  						"node_node01_init_containers_state_running":    0,
   444  						"node_node01_init_containers_state_terminated": 0,
   445  						"node_node01_init_containers_state_waiting":    0,
   446  						"node_node01_pods_cond_containersready":        0,
   447  						"node_node01_pods_cond_podinitialized":         0,
   448  						"node_node01_pods_cond_podready":               0,
   449  						"node_node01_pods_cond_podscheduled":           0,
   450  						"node_node01_pods_phase_failed":                0,
   451  						"node_node01_pods_phase_pending":               0,
   452  						"node_node01_pods_phase_running":               0,
   453  						"node_node01_pods_phase_succeeded":             0,
   454  						"node_node01_pods_readiness":                   0,
   455  						"node_node01_pods_readiness_ready":             0,
   456  						"node_node01_pods_readiness_unready":           0,
   457  					}
   458  					copyAge(expected, mx)
   459  
   460  					assert.Equal(t, expected, mx)
   461  					assert.Equal(t,
   462  						len(nodeChartsTmpl)+len(podChartsTmpl)+len(containerChartsTmpl)*len(pod.Spec.Containers)+len(baseCharts),
   463  						len(*ks.Charts()),
   464  					)
   465  					assert.Equal(t,
   466  						len(podChartsTmpl)+len(containerChartsTmpl)*len(pod.Spec.Containers),
   467  						calcObsoleteCharts(*ks.Charts()),
   468  					)
   469  				}
   470  
   471  				return testCase{
   472  					client: client,
   473  					steps:  []testCaseStep{step1, step2},
   474  				}
   475  			},
   476  		},
   477  		"slow spec.NodeName set": {
   478  			create: func(t *testing.T) testCase {
   479  				ctx := context.Background()
   480  				node := newNode("node01")
   481  				podOrig := newPod(node.Name, "pod01")
   482  				podOrig.Spec.NodeName = ""
   483  				client := fake.NewSimpleClientset(
   484  					node,
   485  					podOrig,
   486  				)
   487  				podUpdated := newPod(node.Name, "pod01") // with set Spec.NodeName
   488  
   489  				step1 := func(t *testing.T, ks *KubeState) {
   490  					_ = ks.Collect()
   491  					for _, c := range *ks.Charts() {
   492  						if strings.HasPrefix(c.ID, "pod_") {
   493  							ok := isLabelValueSet(c, labelKeyNodeName)
   494  							assert.Falsef(t, ok, "chart '%s' has not empty %s label", c.ID, labelKeyNodeName)
   495  						}
   496  					}
   497  				}
   498  				step2 := func(t *testing.T, ks *KubeState) {
   499  					_, _ = client.CoreV1().Pods(podOrig.Namespace).Update(ctx, podUpdated, metav1.UpdateOptions{})
   500  					time.Sleep(time.Millisecond * 50)
   501  					_ = ks.Collect()
   502  
   503  					for _, c := range *ks.Charts() {
   504  						if strings.HasPrefix(c.ID, "pod_") {
   505  							ok := isLabelValueSet(c, labelKeyNodeName)
   506  							assert.Truef(t, ok, "chart '%s' has empty %s label", c.ID, labelKeyNodeName)
   507  						}
   508  					}
   509  				}
   510  
   511  				return testCase{
   512  					client: client,
   513  					steps:  []testCaseStep{step1, step2},
   514  				}
   515  			},
   516  		},
   517  		"add a Pod in runtime": {
   518  			create: func(t *testing.T) testCase {
   519  				ctx := context.Background()
   520  				node := newNode("node01")
   521  				pod1 := newPod(node.Name, "pod01")
   522  				pod2 := newPod(node.Name, "pod02")
   523  				client := fake.NewSimpleClientset(
   524  					node,
   525  					pod1,
   526  				)
   527  				step1 := func(t *testing.T, ks *KubeState) {
   528  					_ = ks.Collect()
   529  					_, _ = client.CoreV1().Pods(pod1.Namespace).Create(ctx, pod2, metav1.CreateOptions{})
   530  				}
   531  
   532  				step2 := func(t *testing.T, ks *KubeState) {
   533  					mx := ks.Collect()
   534  					expected := map[string]int64{
   535  						"discovery_node_discoverer_state":                         1,
   536  						"discovery_pod_discoverer_state":                          1,
   537  						"node_node01_age":                                         4,
   538  						"node_node01_alloc_cpu_limits_used":                       800,
   539  						"node_node01_alloc_cpu_limits_util":                       22857,
   540  						"node_node01_alloc_cpu_requests_used":                     400,
   541  						"node_node01_alloc_cpu_requests_util":                     11428,
   542  						"node_node01_alloc_mem_limits_used":                       838860800,
   543  						"node_node01_alloc_mem_limits_util":                       22857,
   544  						"node_node01_alloc_mem_requests_used":                     419430400,
   545  						"node_node01_alloc_mem_requests_util":                     11428,
   546  						"node_node01_alloc_pods_allocated":                        2,
   547  						"node_node01_alloc_pods_available":                        108,
   548  						"node_node01_alloc_pods_util":                             1818,
   549  						"node_node01_cond_diskpressure":                           0,
   550  						"node_node01_cond_memorypressure":                         0,
   551  						"node_node01_cond_networkunavailable":                     0,
   552  						"node_node01_cond_pidpressure":                            0,
   553  						"node_node01_cond_ready":                                  1,
   554  						"node_node01_schedulability_schedulable":                  1,
   555  						"node_node01_schedulability_unschedulable":                0,
   556  						"node_node01_containers":                                  4,
   557  						"node_node01_containers_state_running":                    4,
   558  						"node_node01_containers_state_terminated":                 0,
   559  						"node_node01_containers_state_waiting":                    0,
   560  						"node_node01_init_containers":                             2,
   561  						"node_node01_init_containers_state_running":               0,
   562  						"node_node01_init_containers_state_terminated":            2,
   563  						"node_node01_init_containers_state_waiting":               0,
   564  						"node_node01_pods_cond_containersready":                   2,
   565  						"node_node01_pods_cond_podinitialized":                    2,
   566  						"node_node01_pods_cond_podready":                          2,
   567  						"node_node01_pods_cond_podscheduled":                      2,
   568  						"node_node01_pods_phase_failed":                           0,
   569  						"node_node01_pods_phase_pending":                          0,
   570  						"node_node01_pods_phase_running":                          2,
   571  						"node_node01_pods_phase_succeeded":                        0,
   572  						"node_node01_pods_readiness":                              100000,
   573  						"node_node01_pods_readiness_ready":                        2,
   574  						"node_node01_pods_readiness_unready":                      0,
   575  						"pod_default_pod01_age":                                   4,
   576  						"pod_default_pod01_cpu_limits_used":                       400,
   577  						"pod_default_pod01_cpu_requests_used":                     200,
   578  						"pod_default_pod01_mem_limits_used":                       419430400,
   579  						"pod_default_pod01_mem_requests_used":                     209715200,
   580  						"pod_default_pod01_cond_containersready":                  1,
   581  						"pod_default_pod01_cond_podinitialized":                   1,
   582  						"pod_default_pod01_cond_podready":                         1,
   583  						"pod_default_pod01_cond_podscheduled":                     1,
   584  						"pod_default_pod01_container_container1_readiness":        1,
   585  						"pod_default_pod01_container_container1_restarts":         0,
   586  						"pod_default_pod01_container_container1_state_running":    1,
   587  						"pod_default_pod01_container_container1_state_terminated": 0,
   588  						"pod_default_pod01_container_container1_state_waiting":    0,
   589  						"pod_default_pod01_container_container2_readiness":        1,
   590  						"pod_default_pod01_container_container2_restarts":         0,
   591  						"pod_default_pod01_container_container2_state_running":    1,
   592  						"pod_default_pod01_container_container2_state_terminated": 0,
   593  						"pod_default_pod01_container_container2_state_waiting":    0,
   594  						"pod_default_pod01_containers":                            2,
   595  						"pod_default_pod01_containers_state_running":              2,
   596  						"pod_default_pod01_containers_state_terminated":           0,
   597  						"pod_default_pod01_containers_state_waiting":              0,
   598  						"pod_default_pod01_init_containers":                       1,
   599  						"pod_default_pod01_init_containers_state_running":         0,
   600  						"pod_default_pod01_init_containers_state_terminated":      1,
   601  						"pod_default_pod01_init_containers_state_waiting":         0,
   602  						"pod_default_pod01_phase_failed":                          0,
   603  						"pod_default_pod01_phase_pending":                         0,
   604  						"pod_default_pod01_phase_running":                         1,
   605  						"pod_default_pod01_phase_succeeded":                       0,
   606  						"pod_default_pod02_age":                                   4,
   607  						"pod_default_pod02_cpu_limits_used":                       400,
   608  						"pod_default_pod02_cpu_requests_used":                     200,
   609  						"pod_default_pod02_mem_limits_used":                       419430400,
   610  						"pod_default_pod02_mem_requests_used":                     209715200,
   611  						"pod_default_pod02_cond_containersready":                  1,
   612  						"pod_default_pod02_cond_podinitialized":                   1,
   613  						"pod_default_pod02_cond_podready":                         1,
   614  						"pod_default_pod02_cond_podscheduled":                     1,
   615  						"pod_default_pod02_container_container1_readiness":        1,
   616  						"pod_default_pod02_container_container1_restarts":         0,
   617  						"pod_default_pod02_container_container1_state_running":    1,
   618  						"pod_default_pod02_container_container1_state_terminated": 0,
   619  						"pod_default_pod02_container_container1_state_waiting":    0,
   620  						"pod_default_pod02_container_container2_readiness":        1,
   621  						"pod_default_pod02_container_container2_restarts":         0,
   622  						"pod_default_pod02_container_container2_state_running":    1,
   623  						"pod_default_pod02_container_container2_state_terminated": 0,
   624  						"pod_default_pod02_container_container2_state_waiting":    0,
   625  						"pod_default_pod02_containers":                            2,
   626  						"pod_default_pod02_containers_state_running":              2,
   627  						"pod_default_pod02_containers_state_terminated":           0,
   628  						"pod_default_pod02_containers_state_waiting":              0,
   629  						"pod_default_pod02_init_containers":                       1,
   630  						"pod_default_pod02_init_containers_state_running":         0,
   631  						"pod_default_pod02_init_containers_state_terminated":      1,
   632  						"pod_default_pod02_init_containers_state_waiting":         0,
   633  						"pod_default_pod02_phase_failed":                          0,
   634  						"pod_default_pod02_phase_pending":                         0,
   635  						"pod_default_pod02_phase_running":                         1,
   636  						"pod_default_pod02_phase_succeeded":                       0,
   637  					}
   638  					copyAge(expected, mx)
   639  
   640  					assert.Equal(t, expected, mx)
   641  					assert.Equal(t,
   642  						len(nodeChartsTmpl)+
   643  							len(podChartsTmpl)*2+
   644  							len(containerChartsTmpl)*len(pod1.Spec.Containers)+
   645  							len(containerChartsTmpl)*len(pod2.Spec.Containers)+
   646  							len(baseCharts),
   647  						len(*ks.Charts()),
   648  					)
   649  				}
   650  
   651  				return testCase{
   652  					client: client,
   653  					steps:  []testCaseStep{step1, step2},
   654  				}
   655  			},
   656  		},
   657  	}
   658  
   659  	for name, creator := range tests {
   660  		t.Run(name, func(t *testing.T) {
   661  			test := creator.create(t)
   662  
   663  			ks := New()
   664  			ks.newKubeClient = func() (kubernetes.Interface, error) { return test.client, nil }
   665  
   666  			require.True(t, ks.Init())
   667  			require.True(t, ks.Check())
   668  			defer ks.Cleanup()
   669  
   670  			for i, executeStep := range test.steps {
   671  				if i == 0 {
   672  					_ = ks.Collect()
   673  					time.Sleep(ks.initDelay)
   674  				} else {
   675  					time.Sleep(time.Second)
   676  				}
   677  				executeStep(t, ks)
   678  			}
   679  		})
   680  	}
   681  }
   682  
   683  func newNode(name string) *corev1.Node {
   684  	return &corev1.Node{
   685  		ObjectMeta: metav1.ObjectMeta{
   686  			Name:              name,
   687  			CreationTimestamp: metav1.Time{Time: time.Now()},
   688  		},
   689  		Status: corev1.NodeStatus{
   690  			Capacity: corev1.ResourceList{
   691  				corev1.ResourceCPU:    mustQuantity("4000m"),
   692  				corev1.ResourceMemory: mustQuantity("4000Mi"),
   693  				"pods":                mustQuantity("110"),
   694  			},
   695  			Allocatable: corev1.ResourceList{
   696  				corev1.ResourceCPU:    mustQuantity("3500m"),
   697  				corev1.ResourceMemory: mustQuantity("3500Mi"),
   698  				"pods":                mustQuantity("110"),
   699  			},
   700  			Conditions: []corev1.NodeCondition{
   701  				{Type: corev1.NodeReady, Status: corev1.ConditionTrue},
   702  				{Type: corev1.NodeMemoryPressure, Status: corev1.ConditionFalse},
   703  				{Type: corev1.NodeDiskPressure, Status: corev1.ConditionFalse},
   704  				{Type: corev1.NodePIDPressure, Status: corev1.ConditionFalse},
   705  				{Type: corev1.NodeNetworkUnavailable, Status: corev1.ConditionFalse},
   706  			},
   707  		},
   708  	}
   709  }
   710  
   711  func newPod(nodeName, name string) *corev1.Pod {
   712  	return &corev1.Pod{
   713  		ObjectMeta: metav1.ObjectMeta{
   714  			Name:              name,
   715  			Namespace:         corev1.NamespaceDefault,
   716  			CreationTimestamp: metav1.Time{Time: time.Now()},
   717  		},
   718  		Spec: corev1.PodSpec{
   719  			NodeName: nodeName,
   720  			InitContainers: []corev1.Container{
   721  				{
   722  					Name: "init-container1",
   723  					Resources: corev1.ResourceRequirements{
   724  						Limits: corev1.ResourceList{
   725  							corev1.ResourceCPU:    mustQuantity("50m"),
   726  							corev1.ResourceMemory: mustQuantity("50Mi"),
   727  						},
   728  						Requests: corev1.ResourceList{
   729  							corev1.ResourceCPU:    mustQuantity("10m"),
   730  							corev1.ResourceMemory: mustQuantity("10Mi"),
   731  						},
   732  					},
   733  				},
   734  			},
   735  			Containers: []corev1.Container{
   736  				{
   737  					Name: "container1",
   738  					Resources: corev1.ResourceRequirements{
   739  						Limits: corev1.ResourceList{
   740  							corev1.ResourceCPU:    mustQuantity("200m"),
   741  							corev1.ResourceMemory: mustQuantity("200Mi"),
   742  						},
   743  						Requests: corev1.ResourceList{
   744  							corev1.ResourceCPU:    mustQuantity("100m"),
   745  							corev1.ResourceMemory: mustQuantity("100Mi"),
   746  						},
   747  					},
   748  				},
   749  				{
   750  					Name: "container2",
   751  					Resources: corev1.ResourceRequirements{
   752  						Limits: corev1.ResourceList{
   753  							corev1.ResourceCPU:    mustQuantity("200m"),
   754  							corev1.ResourceMemory: mustQuantity("200Mi")},
   755  						Requests: corev1.ResourceList{
   756  							corev1.ResourceCPU:    mustQuantity("100m"),
   757  							corev1.ResourceMemory: mustQuantity("100Mi"),
   758  						},
   759  					},
   760  				},
   761  			},
   762  		},
   763  		Status: corev1.PodStatus{
   764  			Phase: corev1.PodRunning,
   765  			Conditions: []corev1.PodCondition{
   766  				{Type: corev1.PodReady, Status: corev1.ConditionTrue},
   767  				{Type: corev1.PodScheduled, Status: corev1.ConditionTrue},
   768  				{Type: corev1.PodInitialized, Status: corev1.ConditionTrue},
   769  				{Type: corev1.ContainersReady, Status: corev1.ConditionTrue},
   770  			},
   771  			InitContainerStatuses: []corev1.ContainerStatus{
   772  				{
   773  					Name:  "init-container1",
   774  					State: corev1.ContainerState{Terminated: &corev1.ContainerStateTerminated{}},
   775  				},
   776  			},
   777  			ContainerStatuses: []corev1.ContainerStatus{
   778  				{
   779  					Name:  "container1",
   780  					Ready: true,
   781  					State: corev1.ContainerState{Running: &corev1.ContainerStateRunning{}},
   782  				},
   783  				{
   784  					Name:  "container2",
   785  					Ready: true,
   786  					State: corev1.ContainerState{Running: &corev1.ContainerStateRunning{}},
   787  				},
   788  			},
   789  		},
   790  	}
   791  }
   792  
   793  type brokenInfoKubeClient struct {
   794  	kubernetes.Interface
   795  }
   796  
   797  func (kc *brokenInfoKubeClient) Discovery() discovery.DiscoveryInterface {
   798  	return &brokenInfoDiscovery{kc.Interface.Discovery()}
   799  }
   800  
   801  type brokenInfoDiscovery struct {
   802  	discovery.DiscoveryInterface
   803  }
   804  
   805  func (d *brokenInfoDiscovery) ServerVersion() (*version.Info, error) {
   806  	return nil, errors.New("brokenInfoDiscovery.ServerVersion() error")
   807  }
   808  
   809  func calcObsoleteCharts(charts module.Charts) (num int) {
   810  	for _, c := range charts {
   811  		if c.Obsolete {
   812  			num++
   813  		}
   814  	}
   815  	return num
   816  }
   817  
   818  func mustQuantity(s string) apiresource.Quantity {
   819  	q, err := apiresource.ParseQuantity(s)
   820  	if err != nil {
   821  		panic(fmt.Sprintf("fail to create resource quantity: %v", err))
   822  	}
   823  	return q
   824  }
   825  
   826  func copyAge(dst, src map[string]int64) {
   827  	for k, v := range src {
   828  		if !strings.HasSuffix(k, "_age") {
   829  			continue
   830  		}
   831  		if _, ok := dst[k]; ok {
   832  			dst[k] = v
   833  		}
   834  	}
   835  }
   836  
   837  func isLabelValueSet(c *module.Chart, name string) bool {
   838  	for _, l := range c.Labels {
   839  		if l.Key == name {
   840  			return l.Value != ""
   841  		}
   842  	}
   843  	return false
   844  }