github.com/verrazzano/verrazzano-monitoring-operator@v0.0.30/pkg/opensearch/health_test.go (about)

     1  // Copyright (C) 2022, Oracle and/or its affiliates.
     2  // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
     3  
     4  package opensearch
     5  
     6  import (
     7  	"context"
     8  	"errors"
     9  	"io"
    10  	"net/http"
    11  	"strings"
    12  	"testing"
    13  
    14  	"github.com/stretchr/testify/assert"
    15  	vmcontrollerv1 "github.com/verrazzano/verrazzano-monitoring-operator/pkg/apis/vmcontroller/v1"
    16  	"github.com/verrazzano/verrazzano-monitoring-operator/pkg/config"
    17  	"github.com/verrazzano/verrazzano-monitoring-operator/pkg/constants"
    18  	appsv1 "k8s.io/api/apps/v1"
    19  	v1 "k8s.io/api/core/v1"
    20  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    21  	"k8s.io/apimachinery/pkg/labels"
    22  	kubeinformers "k8s.io/client-go/informers"
    23  	"k8s.io/client-go/kubernetes"
    24  	fake "k8s.io/client-go/kubernetes/fake"
    25  	appslistersv1 "k8s.io/client-go/listers/apps/v1"
    26  )
    27  
    28  const (
    29  	wrongNodeVersion = `{
    30  	"nodes": {
    31  		"1": {
    32  			"version": "1.2.3",
    33  			"roles": [
    34  				"master"
    35  			]
    36  		},
    37  		"2": {
    38  			"version": "1.2.3",
    39  			"roles": [
    40  				"ingest"
    41  			]
    42  		},
    43  		"3": {
    44  			"version": "1.2.3",
    45  			"roles": [
    46  				"data"
    47  			]
    48  		},
    49  		"4": {
    50  			"version": "1.2.0",
    51  			"roles": [
    52  				"data"
    53  			]
    54  		},
    55  		"5": {
    56  			"version": "1.2.3",
    57  			"roles": [
    58  				"data"
    59  			]
    60  		}
    61  	}
    62  }`
    63  	wrongCountNodes = `{
    64  	"nodes": {
    65  		"1": {
    66  			"version": "1.2.3",
    67  			"roles": [
    68  				"master"
    69  			]
    70  		}
    71  	}
    72  }`
    73  	healthyNodes = `{
    74  	"nodes": {
    75  		"1": {
    76  			"version": "1.2.3",
    77  			"roles": [
    78  				"master"
    79  			]
    80  		},
    81  		"2": {
    82  			"version": "1.2.3",
    83  			"roles": [
    84  				"ingest"
    85  			]
    86  		},
    87  		"3": {
    88  			"version": "1.2.3",
    89  			"roles": [
    90  				"data"
    91  			]
    92  		},
    93  		"4": {
    94  			"version": "1.2.3",
    95  			"roles": [
    96  				"data"
    97  			]
    98  		},
    99  		"5": {
   100  			"version": "1.2.3",
   101  			"roles": [
   102  				"data"
   103  			]
   104  		}
   105  	}
   106  }`
   107  	healthyCluster = `{
   108  	"status": "green",
   109      "number_of_data_nodes": 3
   110  }`
   111  	unhealthyClusterStatus = `{
   112  		"status": "yellow",
   113  		"number_of_data_nodes": 3
   114  }`
   115  )
   116  
   117  var testvmo = vmcontrollerv1.VerrazzanoMonitoringInstance{
   118  	ObjectMeta: metav1.ObjectMeta{
   119  		Name:      "system",
   120  		Namespace: constants.VerrazzanoSystemNamespace,
   121  	},
   122  	Spec: vmcontrollerv1.VerrazzanoMonitoringInstanceSpec{
   123  		Elasticsearch: vmcontrollerv1.Elasticsearch{
   124  			DataNode: vmcontrollerv1.ElasticsearchNode{
   125  				Replicas: 3,
   126  			},
   127  			IngestNode: vmcontrollerv1.ElasticsearchNode{
   128  				Replicas: 1,
   129  			},
   130  			MasterNode: vmcontrollerv1.ElasticsearchNode{
   131  				Replicas: 1,
   132  			},
   133  		},
   134  	},
   135  }
   136  
   137  var statefulSetLister = kubeinformers.NewSharedInformerFactory(fake.NewSimpleClientset(), constants.ResyncPeriod).Apps().V1().StatefulSets().Lister()
   138  
   139  func mockHTTPGenerator(body1, body2 string, code1, code2 int) func(request *http.Request) (*http.Response, error) {
   140  	return func(request *http.Request) (*http.Response, error) {
   141  		if strings.Contains(request.URL.Path, "_cluster/health") {
   142  			return &http.Response{
   143  				StatusCode: code1,
   144  				Body:       io.NopCloser(strings.NewReader(body1)),
   145  			}, nil
   146  		}
   147  		return &http.Response{
   148  			StatusCode: code2,
   149  			Body:       io.NopCloser(strings.NewReader(body2)),
   150  		}, nil
   151  	}
   152  }
   153  
   154  func TestIsOpenSearchHealthy(t *testing.T) {
   155  	config.ESWaitTargetVersion = "1.2.3"
   156  	var tests = []struct {
   157  		name     string
   158  		httpFunc func(request *http.Request) (*http.Response, error)
   159  		isError  bool
   160  	}{
   161  		{
   162  			"healthy when cluster health is green and nodes are ready",
   163  			mockHTTPGenerator(healthyCluster, healthyNodes, 200, 200),
   164  			false,
   165  		},
   166  		{
   167  			"unhealthy when cluster health is yellow",
   168  			mockHTTPGenerator(unhealthyClusterStatus, healthyNodes, 200, 200),
   169  			true,
   170  		},
   171  		{
   172  			"unhealthy when expected node version is not all updated",
   173  			mockHTTPGenerator(healthyNodes, wrongNodeVersion, 200, 200),
   174  			true,
   175  		},
   176  		{
   177  			"unhealthy when expected node version is not all updated",
   178  			mockHTTPGenerator(healthyNodes, wrongCountNodes, 200, 200),
   179  			true,
   180  		},
   181  		{
   182  			"unhealthy when cluster status code is not OK",
   183  			mockHTTPGenerator(healthyCluster, healthyNodes, 403, 200),
   184  			true,
   185  		},
   186  		{
   187  			"unhealthy when cluster is unreachable",
   188  			func(request *http.Request) (*http.Response, error) {
   189  				return nil, errors.New("boom")
   190  			},
   191  			true,
   192  		},
   193  	}
   194  
   195  	for _, tt := range tests {
   196  		t.Run(tt.name, func(t *testing.T) {
   197  			o := NewOSClient(statefulSetLister)
   198  			o.DoHTTP = tt.httpFunc
   199  			err := o.IsUpdated(&testvmo)
   200  			if tt.isError {
   201  				assert.Error(t, err)
   202  			} else {
   203  				assert.NoError(t, err)
   204  			}
   205  		})
   206  	}
   207  }
   208  
   209  func TestIsOpenSearchResizable(t *testing.T) {
   210  	var notEnoughNodesVMO = vmcontrollerv1.VerrazzanoMonitoringInstance{
   211  		ObjectMeta: metav1.ObjectMeta{
   212  			Name:      "system",
   213  			Namespace: constants.VerrazzanoSystemNamespace,
   214  		},
   215  		Spec: vmcontrollerv1.VerrazzanoMonitoringInstanceSpec{
   216  			Elasticsearch: vmcontrollerv1.Elasticsearch{
   217  				DataNode: vmcontrollerv1.ElasticsearchNode{
   218  					Replicas: 1,
   219  				},
   220  				IngestNode: vmcontrollerv1.ElasticsearchNode{
   221  					Replicas: 1,
   222  				},
   223  				MasterNode: vmcontrollerv1.ElasticsearchNode{
   224  					Replicas: 1,
   225  				},
   226  			},
   227  		},
   228  	}
   229  	o := NewOSClient(statefulSetLister)
   230  	assert.Error(t, o.IsDataResizable(&notEnoughNodesVMO))
   231  }
   232  
   233  // simple StatefulsetLister implementation
   234  type simpleStatefulSetLister struct {
   235  	kubeClient kubernetes.Interface
   236  }
   237  
   238  // lists all StatefulSets
   239  func (s *simpleStatefulSetLister) List(selector labels.Selector) ([]*appsv1.StatefulSet, error) {
   240  	namespaces, err := s.kubeClient.CoreV1().Namespaces().List(context.TODO(), metav1.ListOptions{})
   241  	if err != nil {
   242  		return nil, err
   243  	}
   244  	var statefulSets []*appsv1.StatefulSet
   245  	for _, namespace := range namespaces.Items {
   246  
   247  		list, err := s.StatefulSets(namespace.Name).List(selector)
   248  		if err != nil {
   249  			return nil, err
   250  		}
   251  		statefulSets = append(statefulSets, list...)
   252  	}
   253  	return statefulSets, nil
   254  }
   255  
   256  // StatefulSets returns an object that can list and get StatefulSets.
   257  func (s *simpleStatefulSetLister) StatefulSets(namespace string) appslistersv1.StatefulSetNamespaceLister {
   258  	return simpleStatefulSetNamespaceLister{
   259  		namespace:  namespace,
   260  		kubeClient: s.kubeClient,
   261  	}
   262  }
   263  
   264  // GetPodStatefulSets is a fake implementation for StatefulSetLister.GetPodStatefulSets
   265  func (s *simpleStatefulSetLister) GetPodStatefulSets(pod *v1.Pod) ([]*appsv1.StatefulSet, error) {
   266  	return nil, nil
   267  }
   268  
   269  // simpleStatefulSetNamespaceLister implements the StatefulSetNamespaceLister
   270  // interface.
   271  type simpleStatefulSetNamespaceLister struct {
   272  	namespace  string
   273  	kubeClient kubernetes.Interface
   274  }
   275  
   276  // List lists all StatefulSets for a given namespace.
   277  func (s simpleStatefulSetNamespaceLister) List(selector labels.Selector) ([]*appsv1.StatefulSet, error) {
   278  	var statefulSets []*appsv1.StatefulSet
   279  
   280  	list, err := s.kubeClient.AppsV1().StatefulSets(s.namespace).List(context.TODO(), metav1.ListOptions{})
   281  	if err != nil {
   282  		return nil, err
   283  	}
   284  	for i := range list.Items {
   285  		if selector.Matches(labels.Set(list.Items[i].Labels)) {
   286  			statefulSets = append(statefulSets, &list.Items[i])
   287  		}
   288  	}
   289  	return statefulSets, nil
   290  }
   291  
   292  // GetPodStatefulSets is a fake implementation for StatefulSetNamespaceLister.GetPodStatefulSets
   293  func (s *simpleStatefulSetNamespaceLister) GetPodStatefulSets(pod *v1.Pod) ([]*appsv1.StatefulSet, error) {
   294  	return nil, nil
   295  }
   296  
   297  // Get is a fake implementation for StatefulSetNamespaceLister.Get
   298  func (s simpleStatefulSetNamespaceLister) Get(name string) (*appsv1.StatefulSet, error) {
   299  	return nil, nil
   300  }
   301  
   302  // TestIsOpenSearchReady Tests the value returned by  IsOpenSearchReady in few scenarios
   303  // GIVEN a default VMI instance
   304  // WHEN I call IsOpenSearchReady
   305  // THEN the IsOpenSearchReady returns true only when the Opensearch StatefulSet pods are ready, false otherwise
   306  func TestIsOpenSearchReady(t *testing.T) {
   307  	var tests = []struct {
   308  		name      string
   309  		clientSet *fake.Clientset
   310  		isReady   bool
   311  	}{
   312  		{
   313  			"not ready when no stateful set is found",
   314  			fake.NewSimpleClientset(),
   315  			false,
   316  		},
   317  		{
   318  			"not ready when opensearch statefulset is not found",
   319  			fake.NewSimpleClientset(&appsv1.StatefulSet{}),
   320  			false,
   321  		},
   322  		{
   323  			"not ready when opensearch statefulset set is not ready",
   324  			fake.NewSimpleClientset(&appsv1.StatefulSet{
   325  				ObjectMeta: metav1.ObjectMeta{
   326  					Labels: map[string]string{
   327  						constants.VMOLabel: constants.VMODefaultName, constants.ComponentLabel: constants.ComponentOpenSearchValue,
   328  					},
   329  					Namespace: testvmo.GetNamespace(),
   330  				},
   331  				Status: appsv1.StatefulSetStatus{
   332  					Replicas: 1,
   333  				},
   334  			}),
   335  			false,
   336  		},
   337  		{
   338  			"ready when opensearch statefulset set is ready",
   339  			fake.NewSimpleClientset(&appsv1.StatefulSet{
   340  				ObjectMeta: metav1.ObjectMeta{
   341  					Labels: map[string]string{
   342  						constants.VMOLabel: constants.VMODefaultName, constants.ComponentLabel: constants.ComponentOpenSearchValue,
   343  					},
   344  					Namespace: testvmo.GetNamespace(),
   345  				},
   346  				Status: appsv1.StatefulSetStatus{
   347  					Replicas:      1,
   348  					ReadyReplicas: 1,
   349  				},
   350  			}),
   351  			true,
   352  		},
   353  	}
   354  
   355  	for _, tt := range tests {
   356  		t.Run(tt.name, func(t *testing.T) {
   357  			o := NewOSClient(&simpleStatefulSetLister{kubeClient: tt.clientSet})
   358  			ready := o.IsOpenSearchReady(&testvmo)
   359  			assert.Equal(t, ready, tt.isReady)
   360  		})
   361  	}
   362  }
   363  
   364  // TestSetAutoExpandIndicesNoErrorWhenOSNotReady Tests the SetAutoExpandIndices does not return error when OpenSearch is not ready
   365  // GIVEN a default VMI instance
   366  // WHEN I call SetAutoExpandIndices
   367  // THEN the SetAutoExpandIndices does not return error when the Opensearch StatefulSet pods are not ready
   368  func TestSetAutoExpandIndicesNoErrorWhenOSNotReady(t *testing.T) {
   369  	o := NewOSClient(&simpleStatefulSetLister{kubeClient: fake.NewSimpleClientset(&appsv1.StatefulSet{
   370  		ObjectMeta: metav1.ObjectMeta{
   371  			Labels: map[string]string{
   372  				constants.VMOLabel: constants.VMODefaultName, constants.ComponentLabel: constants.ComponentOpenSearchValue,
   373  			},
   374  			Namespace: testvmo.GetNamespace(),
   375  		},
   376  		Status: appsv1.StatefulSetStatus{
   377  			Replicas: 1,
   378  		},
   379  	})})
   380  	assert.NoError(t, <-o.SetAutoExpandIndices(&vmcontrollerv1.VerrazzanoMonitoringInstance{Spec: vmcontrollerv1.VerrazzanoMonitoringInstanceSpec{
   381  		Elasticsearch: vmcontrollerv1.Elasticsearch{
   382  			Enabled: true,
   383  			MasterNode: vmcontrollerv1.ElasticsearchNode{
   384  				Replicas: 1,
   385  				Roles:    []vmcontrollerv1.NodeRole{vmcontrollerv1.MasterRole},
   386  			},
   387  		},
   388  	}}))
   389  }