github.com/verrazzano/verrazzano-monitoring-operator@v0.0.30/pkg/opensearch/opensearch_upgrade.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 vmcontrollerv1 "github.com/verrazzano/verrazzano-monitoring-operator/pkg/apis/vmcontroller/v1" 11 "github.com/verrazzano/verrazzano-monitoring-operator/pkg/config" 12 "github.com/verrazzano/verrazzano-monitoring-operator/pkg/resources" 13 "github.com/verrazzano/verrazzano-monitoring-operator/pkg/util/logs/vzlog" 14 "io/ioutil" 15 "net/http" 16 "regexp" 17 "strconv" 18 "strings" 19 ) 20 21 var ( 22 secondsPerMinute = uint64(60) 23 secondsPerHour = secondsPerMinute * 60 24 secondsPerDay = secondsPerHour * 24 25 unitMultipliers = map[uint8]uint64{ 26 's': 1, 27 'm': secondsPerMinute, 28 'h': secondsPerHour, 29 'd': secondsPerDay, 30 } 31 ) 32 33 type ( 34 ReindexPayload struct { 35 Conflicts string `json:"conflicts"` 36 Source `json:"source"` 37 Dest `json:"dest"` 38 } 39 40 Source struct { 41 Index string `json:"index"` 42 Query *Query `json:"query,omitempty"` 43 } 44 45 Query struct { 46 Range `json:"range"` 47 } 48 49 Range struct { 50 Timestamp `json:"@timestamp"` 51 } 52 53 Timestamp struct { 54 GreaterThanEqual string `json:"gte"` 55 LessThan string `json:"lt"` 56 } 57 58 Dest struct { 59 Index string `json:"index"` 60 OpType string `json:"op_type"` 61 } 62 ) 63 64 // Reindex old style indices to data streams and delete it 65 func (o *OSClient) MigrateIndicesToDataStreams(log vzlog.VerrazzanoLogger, vmi *vmcontrollerv1.VerrazzanoMonitoringInstance, openSearchEndpoint string) error { 66 log.Debugf("Checking for OpenSearch indices to migrate to data streams.") 67 // Get the indices 68 indices, err := o.getIndices(log, openSearchEndpoint) 69 if err != nil { 70 return fmt.Errorf("failed to retrieve OpenSearch index list: %v", err) 71 } 72 systemIndices := getSystemIndices(log, indices) 73 appIndices := getApplicationIndices(log, indices) 74 75 if len(systemIndices) > 0 || len(appIndices) > 0 { 76 log.Info("Migrating Verrazzano indices to data streams") 77 } 78 79 // Reindex and delete old system indices 80 err = o.reindexAndDeleteIndices(log, vmi, openSearchEndpoint, systemIndices, true) 81 if err != nil { 82 return fmt.Errorf("failed to migrate the Verrazzano system indices to data streams: %v", err) 83 } 84 85 // Reindex and delete old application indices 86 err = o.reindexAndDeleteIndices(log, vmi, openSearchEndpoint, appIndices, false) 87 if err != nil { 88 return fmt.Errorf("failed to migrate the Verrazzano application indices to data streams: %v", err) 89 } 90 if len(systemIndices) > 0 || len(appIndices) > 0 { 91 log.Info("Migration of Verrazzano indices to data streams completed successfully") 92 } else { 93 log.Debug("Found no indices to migrate to data streams") 94 } 95 return nil 96 } 97 98 func (o *OSClient) DataStreamExists(openSearchEndpoint, dataStream string) (bool, error) { 99 url := fmt.Sprintf("%s/_data_stream/%s", openSearchEndpoint, dataStream) 100 req, err := http.NewRequest("GET", url, nil) 101 if err != nil { 102 return false, err 103 } 104 resp, err := o.DoHTTP(req) 105 if err != nil { 106 return false, err 107 } 108 if resp.StatusCode == http.StatusNotFound { 109 return false, nil 110 } 111 if resp.StatusCode != http.StatusOK { 112 return false, fmt.Errorf("got code %d when checking for data stream %s", resp.StatusCode, config.DataStreamName()) 113 } 114 return true, nil 115 } 116 117 func getSystemIndices(log vzlog.VerrazzanoLogger, indices []string) []string { 118 var systemIndices []string 119 for _, index := range indices { 120 if strings.HasPrefix(index, "verrazzano-namespace-") { 121 for _, systemNamespace := range config.SystemNamespaces() { 122 if index == "verrazzano-namespace-"+systemNamespace { 123 systemIndices = append(systemIndices, index) 124 } 125 } 126 } 127 if strings.Contains(index, "verrazzano-systemd-journal") { 128 systemIndices = append(systemIndices, index) 129 } 130 if strings.HasPrefix(index, "verrazzano-logstash-") { 131 systemIndices = append(systemIndices, index) 132 } 133 } 134 log.Debugf("Found Verrazzano system indices %v", systemIndices) 135 return systemIndices 136 } 137 138 func getApplicationIndices(log vzlog.VerrazzanoLogger, indices []string) []string { 139 var appIndices []string 140 for _, index := range indices { 141 systemIndex := false 142 if strings.HasPrefix(index, "verrazzano-namespace-") { 143 for _, systemNamespace := range config.SystemNamespaces() { 144 if index == "verrazzano-namespace-"+systemNamespace { 145 systemIndex = true 146 break 147 } 148 } 149 if !systemIndex { 150 appIndices = append(appIndices, index) 151 } 152 } 153 } 154 log.Debugf("Found Verrazzano application indices %v", appIndices) 155 return appIndices 156 } 157 158 func (o *OSClient) getIndices(log vzlog.VerrazzanoLogger, openSearchEndpoint string) ([]string, error) { 159 indicesURL := fmt.Sprintf("%s/_aliases", openSearchEndpoint) 160 log.Debugf("Executing get indices API %s", indicesURL) 161 req, err := http.NewRequest("GET", indicesURL, nil) 162 if err != nil { 163 return nil, err 164 } 165 166 resp, err := o.DoHTTP(req) 167 if err != nil { 168 return nil, err 169 } 170 defer resp.Body.Close() 171 if resp.StatusCode != http.StatusOK { 172 return nil, fmt.Errorf("got status code %d when getting the indices in OpenSearch", resp.StatusCode) 173 } 174 var indices map[string]interface{} 175 err = json.NewDecoder(resp.Body).Decode(&indices) 176 if err != nil { 177 return nil, fmt.Errorf("failed to unmarshall indices response: %v", err) 178 } 179 var indexNames []string 180 for index := range indices { 181 indexNames = append(indexNames, index) 182 183 } 184 log.Debugf("Found Verrazzano indices %v", indices) 185 return indexNames, nil 186 } 187 188 func (o *OSClient) reindexAndDeleteIndices(log vzlog.VerrazzanoLogger, vmi *vmcontrollerv1.VerrazzanoMonitoringInstance, 189 openSearchEndpoint string, indices []string, isSystemIndex bool) error { 190 for _, index := range indices { 191 var dataStreamName string 192 if isSystemIndex { 193 dataStreamName = config.DataStreamName() 194 } else { 195 dataStreamName = strings.Replace(index, "verrazzano-namespace", "verrazzano-application", 1) 196 } 197 noOfSecs, err := getRetentionAgeInSeconds(vmi, dataStreamName) 198 if err != nil { 199 return err 200 } 201 log.Infof("Reindexing data from index %v to data stream %s", index, dataStreamName) 202 err = o.reindexToDataStream(log, openSearchEndpoint, index, dataStreamName, noOfSecs) 203 if err != nil { 204 return err 205 } 206 log.Infof("Cleaning up index %v", index) 207 err = o.deleteIndex(log, openSearchEndpoint, index) 208 if err != nil { 209 return err 210 } 211 log.Infof("Successfully cleaned up index %v", index) 212 } 213 return nil 214 } 215 216 func (o *OSClient) reindexToDataStream(log vzlog.VerrazzanoLogger, openSearchEndpoint, sourceName, destName, retentionSeconds string) error { 217 reindexPayload := createReindexPayload(sourceName, destName, retentionSeconds) 218 payload, err := json.Marshal(reindexPayload) 219 if err != nil { 220 return err 221 } 222 reindexURL := fmt.Sprintf("%s/_reindex", openSearchEndpoint) 223 log.Debugf("Executing Reindex API %s", reindexURL) 224 225 req, err := http.NewRequest("POST", reindexURL, bytes.NewReader(payload)) 226 if err != nil { 227 return err 228 } 229 req.Header.Add(contentTypeHeader, applicationJSON) 230 resp, err := o.DoHTTP(req) 231 if err != nil { 232 log.Errorf("Reindex from %s to %s failed", sourceName, destName) 233 return err 234 } 235 defer resp.Body.Close() 236 responseBody, _ := ioutil.ReadAll(resp.Body) 237 if resp.StatusCode != http.StatusOK { 238 return fmt.Errorf("got status code %d when reindexing from %s to %s failed: %s", resp.StatusCode, sourceName, 239 destName, string(responseBody)) 240 } 241 242 log.Infof("Reindex from %s to %s completed successfully: %s", sourceName, destName, string(responseBody)) 243 return nil 244 } 245 246 func (o *OSClient) deleteIndex(log vzlog.VerrazzanoLogger, openSearchEndpoint string, indexName string) error { 247 deleteIndexURL := fmt.Sprintf("%s/%s", openSearchEndpoint, indexName) 248 log.Debugf("Executing delete index API %s", deleteIndexURL) 249 req, err := http.NewRequest("DELETE", deleteIndexURL, nil) 250 if err != nil { 251 return err 252 } 253 254 resp, err := o.DoHTTP(req) 255 if err != nil { 256 return fmt.Errorf("failed to delete index %s: %v", indexName, err) 257 } 258 defer resp.Body.Close() 259 responseBody, _ := ioutil.ReadAll(resp.Body) 260 log.Debugf("Delete API response %s", string(responseBody)) 261 if resp.StatusCode != http.StatusOK { 262 return fmt.Errorf("got status code %d when deleting the indice %s in OpenSearch: %s", resp.StatusCode, indexName, string(responseBody)) 263 } 264 return nil 265 } 266 267 func createReindexPayload(source, dest, retentionSeconds string) *ReindexPayload { 268 reindexPayload := &ReindexPayload{ 269 Conflicts: "proceed", 270 Source: Source{ 271 Index: source, 272 }, 273 Dest: Dest{ 274 Index: dest, 275 OpType: "create", 276 }, 277 } 278 if retentionSeconds != "" { 279 reindexPayload.Source.Query = &Query{ 280 Range: Range{ 281 Timestamp: Timestamp{ 282 GreaterThanEqual: fmt.Sprintf("now-%s", retentionSeconds), 283 LessThan: "now/s", 284 }, 285 }, 286 } 287 } 288 289 return reindexPayload 290 } 291 292 func getRetentionAgeInSeconds(vmi *vmcontrollerv1.VerrazzanoMonitoringInstance, indexName string) (string, error) { 293 for _, policy := range vmi.Spec.Elasticsearch.Policies { 294 regexpString := resources.ConvertToRegexp(policy.IndexPattern) 295 matched, _ := regexp.MatchString(regexpString, indexName) 296 if matched { 297 seconds, err := calculateSeconds(*policy.MinIndexAge) 298 if err != nil { 299 return "", fmt.Errorf("failed to calculate the retention age in seconds: %v", err) 300 } 301 return fmt.Sprintf("%ds", seconds), nil 302 303 } 304 } 305 return "", nil 306 } 307 308 func calculateSeconds(age string) (uint64, error) { 309 n := age[:len(age)-1] 310 number, err := strconv.ParseUint(n, 10, 0) 311 if err != nil { 312 return 0, fmt.Errorf("unable to parse the specified time unit %s", n) 313 } 314 unit := age[len(age)-1] 315 result := number * unitMultipliers[unit] 316 if result < 1 { 317 return result, fmt.Errorf("conversion to seconds for time unit %s is unsupported", strconv.Itoa(int(unit))) 318 } 319 return result, nil 320 }