github.com/verrazzano/verrazzano-monitoring-operator@v0.0.30/pkg/opensearch/opensearch.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  	"bytes"
     8  	"encoding/json"
     9  	"fmt"
    10  	"net/http"
    11  
    12  	vmcontrollerv1 "github.com/verrazzano/verrazzano-monitoring-operator/pkg/apis/vmcontroller/v1"
    13  	"github.com/verrazzano/verrazzano-monitoring-operator/pkg/constants"
    14  	"github.com/verrazzano/verrazzano-monitoring-operator/pkg/resources"
    15  	"github.com/verrazzano/verrazzano-monitoring-operator/pkg/resources/nodes"
    16  	"go.uber.org/zap"
    17  	"k8s.io/apimachinery/pkg/labels"
    18  	appslistersv1 "k8s.io/client-go/listers/apps/v1"
    19  )
    20  
    21  type (
    22  	OSClient struct {
    23  		httpClient        *http.Client
    24  		DoHTTP            func(request *http.Request) (*http.Response, error)
    25  		statefulSetLister appslistersv1.StatefulSetLister
    26  	}
    27  )
    28  
    29  const (
    30  	indexSettings     = `{"index_patterns": [".opendistro*"],"priority": 0,"template": {"settings": {"auto_expand_replicas": "0-1"}}}`
    31  	applicationJSON   = "application/json"
    32  	contentTypeHeader = "Content-Type"
    33  )
    34  
    35  func NewOSClient(statefulSetLister appslistersv1.StatefulSetLister) *OSClient {
    36  	o := &OSClient{
    37  		httpClient:        http.DefaultClient,
    38  		statefulSetLister: statefulSetLister,
    39  	}
    40  	o.DoHTTP = func(request *http.Request) (*http.Response, error) {
    41  		return o.httpClient.Do(request)
    42  	}
    43  	return o
    44  }
    45  
    46  // IsDataResizable returns an error unless these conditions of the OpenSearch cluster are met
    47  // - at least 2 data nodes
    48  // - 'green' health
    49  // - all expected nodes are present in the cluster status
    50  func (o *OSClient) IsDataResizable(vmo *vmcontrollerv1.VerrazzanoMonitoringInstance) error {
    51  	if vmo.Spec.Elasticsearch.DataNode.Replicas < MinDataNodesForResize {
    52  		return fmt.Errorf("cannot resize OpenSearch with less than %d data nodes. Scale up your cluster to at least %d data nodes", MinDataNodesForResize, MinDataNodesForResize)
    53  	}
    54  	return o.opensearchHealth(vmo, true, true)
    55  }
    56  
    57  // IsUpdated returns an error unless these conditions of the OpenSearch cluster are met
    58  // - 'green' health
    59  // - all expected nodes are present in the cluster status
    60  func (o *OSClient) IsUpdated(vmo *vmcontrollerv1.VerrazzanoMonitoringInstance) error {
    61  	return o.opensearchHealth(vmo, true, true)
    62  }
    63  
    64  // IsGreen returns an error unless these conditions of the OpenSearch cluster are met
    65  // - 'green' health
    66  func (o *OSClient) IsGreen(vmo *vmcontrollerv1.VerrazzanoMonitoringInstance) error {
    67  	return o.opensearchHealth(vmo, false, false)
    68  }
    69  
    70  // ConfigureISM sets up the ISM Policies
    71  // The returned channel should be read for exactly one response, which tells whether ISM configuration succeeded.
    72  func (o *OSClient) ConfigureISM(vmi *vmcontrollerv1.VerrazzanoMonitoringInstance) chan error {
    73  	ch := make(chan error)
    74  	// configuration is done asynchronously, as this does not need to be blocking
    75  	go func() {
    76  		if !vmi.Spec.Elasticsearch.Enabled {
    77  			ch <- nil
    78  			return
    79  		}
    80  
    81  		if !o.IsOpenSearchReady(vmi) {
    82  			ch <- nil
    83  			return
    84  		}
    85  
    86  		opensearchEndpoint := resources.GetOpenSearchHTTPEndpoint(vmi)
    87  		for _, policy := range vmi.Spec.Elasticsearch.Policies {
    88  			if err := o.createISMPolicy(opensearchEndpoint, policy); err != nil {
    89  				ch <- err
    90  				return
    91  			}
    92  		}
    93  
    94  		ch <- o.cleanupPolicies(opensearchEndpoint, vmi.Spec.Elasticsearch.Policies)
    95  	}()
    96  
    97  	return ch
    98  }
    99  
   100  // SetAutoExpandIndices updates the default index settings to auto expand replicas (max 1) when nodes are added to the cluster
   101  func (o *OSClient) SetAutoExpandIndices(vmi *vmcontrollerv1.VerrazzanoMonitoringInstance) chan error {
   102  	ch := make(chan error)
   103  
   104  	// configuration is done asynchronously, as this does not need to be blocking
   105  	go func() {
   106  		if !vmi.Spec.Elasticsearch.Enabled {
   107  			ch <- nil
   108  			return
   109  		}
   110  		if !nodes.IsSingleNodeCluster(vmi) {
   111  			ch <- nil
   112  			return
   113  		}
   114  
   115  		if !o.IsOpenSearchReady(vmi) {
   116  			ch <- nil
   117  			return
   118  		}
   119  
   120  		opensearchEndpoint := resources.GetOpenSearchHTTPEndpoint(vmi)
   121  		settingsURL := fmt.Sprintf("%s/_index_template/ism-plugin-template", opensearchEndpoint)
   122  		req, err := http.NewRequest("PUT", settingsURL, bytes.NewReader([]byte(indexSettings)))
   123  		if err != nil {
   124  			ch <- err
   125  			return
   126  		}
   127  		req.Header.Add(contentTypeHeader, applicationJSON)
   128  		resp, err := o.DoHTTP(req)
   129  		if err != nil {
   130  			ch <- err
   131  			return
   132  		}
   133  		if resp.StatusCode != http.StatusOK {
   134  			ch <- fmt.Errorf("got status code %d when updating default settings of index, expected %d", resp.StatusCode, http.StatusOK)
   135  			return
   136  		}
   137  		var updatedIndexSettings map[string]bool
   138  		err = json.NewDecoder(resp.Body).Decode(&updatedIndexSettings)
   139  		if err != nil {
   140  			ch <- err
   141  			return
   142  		}
   143  		if !updatedIndexSettings["acknowledged"] {
   144  			ch <- fmt.Errorf("expected acknowldegement for index settings update but did not get. Actual response  %v", updatedIndexSettings)
   145  			return
   146  		}
   147  		ch <- nil
   148  	}()
   149  
   150  	return ch
   151  }
   152  
   153  // IsOpenSearchReady returns true when all OpenSearch pods are ready, false otherwise
   154  func (o *OSClient) IsOpenSearchReady(vmi *vmcontrollerv1.VerrazzanoMonitoringInstance) bool {
   155  	selector := labels.SelectorFromSet(map[string]string{constants.VMOLabel: vmi.Name, constants.ComponentLabel: constants.ComponentOpenSearchValue})
   156  	statefulSets, err := o.statefulSetLister.StatefulSets(vmi.Namespace).List(selector)
   157  	if err != nil {
   158  		zap.S().Errorf("error fetching OpenSearch statefulset, error: %s", err.Error())
   159  		return false
   160  	}
   161  
   162  	if len(statefulSets) == 0 {
   163  		zap.S().Warn("waiting for OpenSearch statefulset to be created.")
   164  		return false
   165  	}
   166  
   167  	if len(statefulSets) > 1 {
   168  		zap.S().Errorf("invalid number of OpenSearch statefulset created %v.", len(statefulSets))
   169  		return false
   170  	}
   171  
   172  	return statefulSets[0].Status.ReadyReplicas == statefulSets[0].Status.Replicas
   173  }