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  }