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(¬EnoughNodesVMO)) 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 }