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 }