github.com/verrazzano/verrazzano@v1.7.1/tests/e2e/pkg/elasticsearch.go (about)

     1  // Copyright (c) 2021, 2023, 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 pkg
     5  
     6  import (
     7  	"bytes"
     8  	"context"
     9  	"encoding/json"
    10  	"fmt"
    11  	"html/template"
    12  	"net/http"
    13  	url2 "net/url"
    14  	"os"
    15  	"regexp"
    16  	"strconv"
    17  	"strings"
    18  	"time"
    19  
    20  	"github.com/onsi/gomega"
    21  	vmov1 "github.com/verrazzano/verrazzano-monitoring-operator/pkg/apis/vmcontroller/v1"
    22  	vzapi "github.com/verrazzano/verrazzano/platform-operator/apis/verrazzano/v1beta1"
    23  
    24  	"github.com/hashicorp/go-retryablehttp"
    25  	"github.com/onsi/ginkgo/v2"
    26  	"github.com/verrazzano/verrazzano/pkg/k8sutil"
    27  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    28  )
    29  
    30  const (
    31  	// ISO8601Layout defines the timestamp format
    32  	ISO8601Layout                = "2006-01-02T15:04:05.999999999-07:00"
    33  	opensearchIndexManagement    = "opensearch-index-management"
    34  	opensearchJobScheduler       = "opensearch-job-scheduler"
    35  	opensearchPrometheusExporter = "prometheus-exporter"
    36  	opensearchAlerting           = "opensearch-alerting"
    37  )
    38  
    39  // GetOpenSearchSystemIndex in Verrazzano 1.3.0, indices in the verrazzano-system namespace have been migrated
    40  // to the verrazzano-system data stream
    41  func GetOpenSearchSystemIndex(name string) (string, error) {
    42  	return GetOpenSearchSystemIndexWithKC(name, "")
    43  }
    44  
    45  // GetOpenSearchSystemIndexWithKC is the same as GetOpenSearchSystemIndex but the kubeconfig may be specified for MC tests
    46  func GetOpenSearchSystemIndexWithKC(name, kubeconfigPath string) (string, error) {
    47  	usingDataStreams, err := isUsingDataStreams(kubeconfigPath)
    48  	if err != nil {
    49  		return "", err
    50  	}
    51  	if usingDataStreams {
    52  		return "verrazzano-system", nil
    53  	}
    54  	if name == "systemd-journal" {
    55  		return "verrazzano-systemd-journal", nil
    56  	}
    57  	return "verrazzano-namespace-" + name, nil
    58  }
    59  
    60  // Retention/Rollover policy names in ISM plugin
    61  const (
    62  	SystemLogIsmPolicyName      = "verrazzano-system"
    63  	ApplicationLogIsmPolicyName = "verrazzano-application"
    64  )
    65  
    66  // Error logging formats
    67  const (
    68  	queryErrorFormat      = "Error retrieving Opensearch query results: url=%s, error=%s"
    69  	queryStatusFormat     = "Error retrieving Opensearch query results: url=%s, status=%d"
    70  	kubeconfigErrorFormat = "Error getting kubeconfig: %v"
    71  )
    72  
    73  // URL formats
    74  const (
    75  	getDataStreamURLFormat    = "%s/_data_stream/"
    76  	listDataStreamURLFormat   = "%s/_data_stream"
    77  	deleteDataStreamURLFormat = "%s/_data_stream/%s"
    78  )
    79  
    80  // Default values for Retention period and Rollover period
    81  var (
    82  	DefaultRetentionPeriod = "7d"
    83  	DefaultRolloverPeriod  = "1d"
    84  )
    85  
    86  type PolicyList struct {
    87  	Policies      []InlinePolicy `json:"policies"`
    88  	TotalPolicies int            `json:"total_policies"`
    89  }
    90  
    91  type InlinePolicy struct {
    92  	ID             *string   `json:"_id,omitempty"`
    93  	PrimaryTerm    *int      `json:"_primary_term,omitempty"`
    94  	SequenceNumber *int      `json:"_seq_no,omitempty"`
    95  	Status         *int      `json:"status,omitempty"`
    96  	Policy         ISMPolicy `json:"policy"`
    97  }
    98  
    99  // ISMPolicy definition
   100  type ISMPolicy struct {
   101  	PolicyID        string      `json:"policy_id"`
   102  	Description     string      `json:"description"`
   103  	LastUpdatedTime int64       `json:"last_updated_time"`
   104  	SchemaVersion   int         `json:"schema_version"`
   105  	DefaultState    string      `json:"default_state"`
   106  	States          []State     `json:"states"`
   107  	IsmTemplate     IsmTemplate `json:"ism_template"`
   108  }
   109  
   110  // State defined in ISM policy
   111  type State struct {
   112  	Name        string       `json:"name"`
   113  	Actions     []Action     `json:"actions"`
   114  	Transitions []Transition `json:"transitions"`
   115  }
   116  
   117  // Rollover or Delete action defined in ISM policy
   118  type Action struct {
   119  	Rollover struct {
   120  		MinIndexAge string `json:"min_index_age"`
   121  	} `json:"rollover,omitempty"`
   122  	Delete struct {
   123  		MinIndexAge string `json:"min_index_age"`
   124  	} `json:"delete,omitempty"`
   125  }
   126  
   127  // Transition defined in ISM policy
   128  type Transition struct {
   129  	StateName  string            `json:"state_name"`
   130  	Conditions map[string]string `json:"conditions"`
   131  }
   132  
   133  // IsmTemplate defined in ISM policy
   134  type IsmTemplate []struct {
   135  	IndexPatterns   []string `json:"index_patterns"`
   136  	Priority        int      `json:"priority"`
   137  	LastUpdatedTime int64    `json:"last_updated_time"`
   138  }
   139  
   140  // IndexMetadata contains information about a particular
   141  type IndexMetadata struct {
   142  	Mapping struct {
   143  		TotalFields struct {
   144  			Limit string `json:"limit"`
   145  		} `json:"total_fields"`
   146  	} `json:"mapping"`
   147  	RefreshInterval    string `json:"refresh_interval"`
   148  	Hidden             string `json:"hidden"`
   149  	NumberOfShards     string `json:"number_of_shards"`
   150  	AutoExpandReplicas string `json:"auto_expand_replicas"`
   151  	ProvidedName       string `json:"provided_name"`
   152  	CreationDate       string `json:"creation_date"`
   153  	NumberOfReplicas   string `json:"number_of_replicas"`
   154  	UUID               string `json:"uuid"`
   155  	Version            struct {
   156  		Created string `json:"created"`
   157  	} `json:"version"`
   158  }
   159  
   160  // IndexSettings  parent object containing the index metadata
   161  type IndexSettings struct {
   162  	Settings struct {
   163  		Index IndexMetadata `json:"index"`
   164  	} `json:"settings"`
   165  }
   166  
   167  // DataStream details
   168  type DataStream struct {
   169  	Name           string `json:"name"`
   170  	TimestampField struct {
   171  		Name string `json:"name"`
   172  	} `json:"timestamp_field"`
   173  	Indices []struct {
   174  		IndexName string `json:"index_name"`
   175  		IndexUUID string `json:"index_uuid"`
   176  	} `json:"indices"`
   177  	Generation int    `json:"generation"`
   178  	Status     string `json:"status"`
   179  	Template   string `json:"template"`
   180  }
   181  
   182  // SearchResult represents the result of an Opensearch search query
   183  type SearchResult struct {
   184  	Took     int  `json:"took"`
   185  	TimedOut bool `json:"timed_out"`
   186  	Shards   struct {
   187  		Total      int `json:"total"`
   188  		Successful int `json:"successful"`
   189  		Skipped    int `json:"skipped"`
   190  		Failed     int `json:"failed"`
   191  	} `json:"_shards"`
   192  	Hits struct {
   193  		Total struct {
   194  			Value    int    `json:"value"`
   195  			Relation string `json:"relation"`
   196  		} `json:"total"`
   197  		MaxScore interface{}   `json:"max_score"`
   198  		Hits     []interface{} `json:"hits"`
   199  	} `json:"hits"`
   200  }
   201  
   202  // IndexListData represents the row of /_cat/indices?format=json output
   203  type IndexListData struct {
   204  	Health       string `json:"health"`
   205  	Status       string `json:"status"`
   206  	Index        string `json:"index"`
   207  	UUID         string `json:"uuid"`
   208  	Pri          string `json:"pri"`
   209  	Rep          string `json:"rep"`
   210  	DocsCount    string `json:"docsCount"`
   211  	DocsDeleted  string `json:"docsDeleted"`
   212  	StoreSize    string `json:"storeSize"`
   213  	PriStoreSize string `json:"priStoreSize"`
   214  }
   215  
   216  type OpenSearchISMPolicyAddModifier struct{}
   217  
   218  type OpenSearchISMPolicyRemoveModifier struct{}
   219  
   220  var expectedSystemISMPolicies = []string{"vz-application", "vz-custom"}
   221  
   222  func (u OpenSearchISMPolicyAddModifier) ModifyCR(cr *vzapi.Verrazzano) {
   223  	cr.Spec.Components.OpenSearch = &vzapi.OpenSearchComponent{}
   224  	if cr.Spec.Components.OpenSearch.Policies == nil {
   225  		cr.Spec.Components.OpenSearch.Policies = []vmov1.IndexManagementPolicy{
   226  			{
   227  				PolicyName:   "verrazzano-system",
   228  				IndexPattern: "verrazzano-system*",
   229  				MinIndexAge:  &DefaultRetentionPeriod,
   230  				Rollover: vmov1.RolloverPolicy{
   231  					MinIndexAge: &DefaultRolloverPeriod,
   232  				},
   233  			},
   234  			{
   235  				PolicyName:   "verrazzano-application",
   236  				IndexPattern: "verrazzano-application*",
   237  				MinIndexAge:  &DefaultRetentionPeriod,
   238  				Rollover: vmov1.RolloverPolicy{
   239  					MinIndexAge: &DefaultRolloverPeriod,
   240  				},
   241  			},
   242  		}
   243  	}
   244  }
   245  
   246  func (u OpenSearchISMPolicyRemoveModifier) ModifyCR(cr *vzapi.Verrazzano) {
   247  	cr.Spec.Components.OpenSearch = &vzapi.OpenSearchComponent{}
   248  }
   249  
   250  func TestOpenSearchPlugins(pollingInterval time.Duration, waitTimeout time.Duration) {
   251  	if UseExternalOpensearch() {
   252  		ginkgo.Skip("Skip External OpenSearch")
   253  	}
   254  	gomega.Eventually(func() error {
   255  		return VerifyOpenSearchPlugins()
   256  	}).WithPolling(pollingInterval).WithTimeout(waitTimeout).Should(gomega.BeNil())
   257  }
   258  
   259  // VerifyOpenSearchPlugins checks that the OpenSearch plugins are installed
   260  func VerifyOpenSearchPlugins() error {
   261  	resp, err := doGetOpenSearchURL("%s/_cat/plugins?format=json")
   262  	if err != nil {
   263  		return err
   264  	}
   265  	if resp.StatusCode == http.StatusOK {
   266  		out := string(resp.Body)
   267  		missingPluginsStr := ""
   268  
   269  		missingPlugins := checkMissingPlugin(out, opensearchJobScheduler, &missingPluginsStr)
   270  		missingPlugins = missingPlugins || checkMissingPlugin(out, opensearchIndexManagement, &missingPluginsStr)
   271  		missingPlugins = missingPlugins || checkMissingPlugin(out, opensearchPrometheusExporter, &missingPluginsStr)
   272  		missingPlugins = missingPlugins || checkMissingPlugin(out, opensearchAlerting, &missingPluginsStr)
   273  
   274  		if missingPlugins {
   275  			return fmt.Errorf("missing OpenSearch plugins that were not installed: %s", missingPluginsStr)
   276  		}
   277  	}
   278  	return nil
   279  }
   280  
   281  func checkMissingPlugin(response string, plugin string, missingPluginsStr *string) bool {
   282  	if !strings.Contains(response, plugin) {
   283  		*missingPluginsStr = *missingPluginsStr + plugin + " "
   284  		return true
   285  	}
   286  	return false
   287  }
   288  
   289  // GetOpenSearchAppIndex in Verrazzano 1.3.0, application indices have been migrated to data streams
   290  // following the pattern 'verrazzano-application-<application name>'
   291  func GetOpenSearchAppIndex(namespace string) (string, error) {
   292  	return GetOpenSearchAppIndexWithKC(namespace, "")
   293  }
   294  
   295  // GetOpenSearchAppIndexWithKC is the same as GetOpenSearchAppIndex but kubeconfig may be specified for MC tests
   296  func GetOpenSearchAppIndexWithKC(namespace, kubeconfigPath string) (string, error) {
   297  	usingDataStreams, err := isUsingDataStreams(kubeconfigPath)
   298  	if err != nil {
   299  		return "", err
   300  	}
   301  	if usingDataStreams {
   302  		return "verrazzano-application-" + namespace, nil
   303  	}
   304  	return "verrazzano-namespace-" + namespace, nil
   305  }
   306  
   307  func isUsingDataStreams(kubeconfigPath string) (bool, error) {
   308  	kubeConfig, err := getKubeConfigPath(kubeconfigPath)
   309  	if err != nil {
   310  		return false, err
   311  	}
   312  	return IsVerrazzanoMinVersion("1.3.0", kubeConfig)
   313  }
   314  
   315  func UseExternalOpensearch() bool {
   316  	return os.Getenv("EXTERNAL_ELASTICSEARCH") == "true"
   317  }
   318  
   319  // GetExternalOpenSearchURL gets the external Opensearch URL
   320  func GetExternalOpenSearchURL(kubeconfigPath string) string {
   321  	opensearchSvc := "opensearch-cluster-master"
   322  	// the equivalent of kubectl get svc opensearchSvc -o=jsonpath='{.status.loadBalancer.ingress[0].ip}'
   323  	clientset, err := GetKubernetesClientsetForCluster(kubeconfigPath)
   324  	if err != nil {
   325  		Log(Error, fmt.Sprintf("Failed to get clientset for cluster %v", err))
   326  		return ""
   327  	}
   328  	svc, err := clientset.CoreV1().Services("default").Get(context.TODO(), opensearchSvc, metav1.GetOptions{})
   329  	if err != nil {
   330  		Log(Info, fmt.Sprintf("Could not get services quickstart-es-http in sockshop: %v\n", err.Error()))
   331  		return ""
   332  	}
   333  	if svc.Status.LoadBalancer.Ingress != nil && len(svc.Status.LoadBalancer.Ingress) > 0 {
   334  		return fmt.Sprintf("https://%s:9200", svc.Status.LoadBalancer.Ingress[0].IP)
   335  	}
   336  	return ""
   337  }
   338  
   339  // GetSystemOpenSearchIngressURL gets the system Opensearch Ingress host in the given cluster
   340  func GetSystemOpenSearchIngressURL(kubeconfigPath string) string {
   341  	clientset, err := GetKubernetesClientsetForCluster(kubeconfigPath)
   342  	if err != nil {
   343  		Log(Error, fmt.Sprintf("Failed to get clientset for cluster %v", err))
   344  		return ""
   345  	}
   346  	// Return the newer OS ingress if >= 1.7.0
   347  	// Else return the vmi ingress as usual
   348  	useNewOSIngress, _ := IsVerrazzanoMinVersion("1.7.0", kubeconfigPath)
   349  	ingressList, _ := clientset.NetworkingV1().Ingresses(VerrazzanoNamespace).List(context.TODO(), metav1.ListOptions{})
   350  	for _, ingress := range ingressList.Items {
   351  		if !useNewOSIngress && (ingress.Name == "vmi-system-os-ingest" || ingress.Name == "vmi-system-es-ingest") {
   352  			Log(Info, fmt.Sprintf("Found Opensearch Ingress %v, host %s", ingress.Name, ingress.Spec.Rules[0].Host))
   353  			return fmt.Sprintf("https://%s", ingress.Spec.Rules[0].Host)
   354  		} else if useNewOSIngress && ingress.Name == "opensearch" {
   355  			Log(Info, fmt.Sprintf("Found Opensearch Ingress %v, host %s", ingress.Name, ingress.Spec.Rules[0].Host))
   356  			return fmt.Sprintf("https://%s", ingress.Spec.Rules[0].Host)
   357  		}
   358  	}
   359  	return ""
   360  }
   361  
   362  // getOpenSearchURL returns VMI or external ES URL depending on env var EXTERNAL_ELASTICSEARCH
   363  func getOpenSearchURL(kubeconfigPath string) string {
   364  	if UseExternalOpensearch() {
   365  		return GetExternalOpenSearchURL(kubeconfigPath)
   366  	}
   367  	return GetSystemOpenSearchIngressURL(kubeconfigPath)
   368  }
   369  
   370  // getOpenSearchUsernamePassword returns the username/password for connecting to opensearch
   371  func getOpenSearchUsernamePassword(kubeconfigPath string) (username, password string, err error) {
   372  	if UseExternalOpensearch() {
   373  		esSecret, err := GetSecretInCluster(VerrazzanoNamespace, "external-es-secret", kubeconfigPath)
   374  		if err != nil {
   375  			Log(Error, fmt.Sprintf("Failed to get external-es-secret secret: %v", err))
   376  			return "", "", err
   377  		}
   378  		return string(esSecret.Data["username"]), string(esSecret.Data["password"]), err
   379  	}
   380  	password, err = GetVerrazzanoPasswordInCluster(kubeconfigPath)
   381  	if err != nil {
   382  		return "", "", err
   383  	}
   384  	return "verrazzano", password, err
   385  }
   386  
   387  // getOpenSearchWithBasicAuth access ES with GET using basic auth, using a given kubeconfig
   388  func getOpenSearchWithBasicAuth(url string, hostHeader string, username string, password string, kubeconfigPath string) (*HTTPResponse, error) {
   389  	retryableClient, err := getOpenSearchClient(kubeconfigPath)
   390  	if err != nil {
   391  		return nil, err
   392  	}
   393  	return doReq(url, "GET", "", hostHeader, username, password, nil, retryableClient)
   394  }
   395  
   396  // postOpenSearchWithBasicAuth retries POST using basic auth
   397  func postOpenSearchWithBasicAuth(url, body, username, password, kubeconfigPath string) (*HTTPResponse, error) {
   398  	retryableClient, err := getOpenSearchClient(kubeconfigPath)
   399  	if err != nil {
   400  		return nil, err
   401  	}
   402  	return doReq(url, "POST", "application/json", "", username, password, strings.NewReader(body), retryableClient)
   403  }
   404  
   405  // putOpenSearchWithBasicAuth retries PUT using basic auth
   406  func putOpenSearchWithBasicAuth(url, body, username, password, kubeconfigPath string) (*HTTPResponse, error) {
   407  	retryableClient, err := getOpenSearchClient(kubeconfigPath)
   408  	if err != nil {
   409  		return nil, err
   410  	}
   411  	return doReq(url, "PUT", "application/json", "", username, password, strings.NewReader(body), retryableClient)
   412  }
   413  
   414  // deleteOpenSearchWithBasicAuth retries DELETE using basic auth
   415  func deleteOpenSearchWithBasicAuth(url, body, username, password, kubeconfigPath string) (*HTTPResponse, error) {
   416  	retryableClient, err := getOpenSearchClient(kubeconfigPath)
   417  	if err != nil {
   418  		return nil, err
   419  	}
   420  	return doReq(url, "DELETE", "application/json", "", username, password, strings.NewReader(body), retryableClient)
   421  }
   422  
   423  // getOpenSearchClient returns ES client to perform http operations
   424  func getOpenSearchClient(kubeconfigPath string) (*retryablehttp.Client, error) {
   425  	var retryableClient *retryablehttp.Client
   426  	var err error
   427  	if UseExternalOpensearch() {
   428  		caCert, err := getExternalESCACert(kubeconfigPath)
   429  		if err != nil {
   430  			return nil, err
   431  		}
   432  		client, err := getHTTPClientWithCABundle(caCert, kubeconfigPath)
   433  		if err != nil {
   434  			return nil, err
   435  		}
   436  		retryableClient = newRetryableHTTPClient(client)
   437  		if err != nil {
   438  			return nil, err
   439  		}
   440  	} else {
   441  		retryableClient, err = GetVerrazzanoHTTPClient(kubeconfigPath)
   442  		if err != nil {
   443  			return nil, err
   444  		}
   445  	}
   446  	return retryableClient, nil
   447  }
   448  
   449  // getExternalESCACert returns the CA cert from external-es-secret in the specified cluster
   450  func getExternalESCACert(kubeconfigPath string) ([]byte, error) {
   451  	clientset, err := GetKubernetesClientsetForCluster(kubeconfigPath)
   452  	if err != nil {
   453  		return nil, err
   454  	}
   455  	certSecret, err := clientset.CoreV1().Secrets(VerrazzanoNamespace).Get(context.TODO(), "external-es-secret", metav1.GetOptions{})
   456  	if err != nil {
   457  		return nil, err
   458  	}
   459  	return certSecret.Data["ca-bundle"], nil
   460  }
   461  
   462  // listSystemOpenSearchIndices lists the system Opensearch indices in the given cluster
   463  func listSystemOpenSearchIndices(kubeconfigPath string) []string {
   464  	list := []string{}
   465  	url := fmt.Sprintf("%s/_all", getOpenSearchURL(kubeconfigPath))
   466  	username, password, err := getOpenSearchUsernamePassword(kubeconfigPath)
   467  	if err != nil {
   468  		return list
   469  	}
   470  	resp, err := getOpenSearchWithBasicAuth(url, "", username, password, kubeconfigPath)
   471  	if err != nil {
   472  		Log(Error, fmt.Sprintf("Error getting Opensearch indices: url=%s, error=%v", url, err))
   473  		return list
   474  	}
   475  	if resp.StatusCode != http.StatusOK {
   476  		Log(Error, fmt.Sprintf("Error retrieving Opensearch indices: url=%s, status=%d", url, resp.StatusCode))
   477  		return list
   478  	}
   479  	Log(Debug, fmt.Sprintf("indices: %s", resp.Body))
   480  	var indexMap map[string]interface{}
   481  	json.Unmarshal(resp.Body, &indexMap)
   482  	for name := range indexMap {
   483  		list = append(list, name)
   484  	}
   485  	return list
   486  }
   487  
   488  // querySystemOpenSearch searches the Opensearch index with the fields in the given cluster
   489  func querySystemOpenSearch(index string, fields map[string]string, kubeconfigPath string, sortQuery bool) map[string]interface{} {
   490  	query := ""
   491  	for name, value := range fields {
   492  		fieldQuery := fmt.Sprintf("%s:%s", name, value)
   493  		if query == "" {
   494  			query = fieldQuery
   495  		} else {
   496  			query = fmt.Sprintf("%s+AND+%s", query, fieldQuery)
   497  		}
   498  	}
   499  	if sortQuery {
   500  		query = query + "&sort=@timestamp:desc&size=10"
   501  	}
   502  
   503  	var result map[string]interface{}
   504  	url := fmt.Sprintf("%s/%s/_search?q=%s", getOpenSearchURL(kubeconfigPath), index, query)
   505  	username, password, err := getOpenSearchUsernamePassword(kubeconfigPath)
   506  	if err != nil {
   507  		return result
   508  	}
   509  	resp, err := getOpenSearchWithBasicAuth(url, "", username, password, kubeconfigPath)
   510  	if err != nil {
   511  		Log(Error, fmt.Sprintf(queryErrorFormat, url, err))
   512  		return result
   513  	}
   514  	if resp.StatusCode != http.StatusOK {
   515  		Log(Error, fmt.Sprintf(queryStatusFormat, url, resp.StatusCode))
   516  		return result
   517  	}
   518  	Log(Debug, fmt.Sprintf("records: %s", resp.Body))
   519  	json.Unmarshal(resp.Body, &result)
   520  	return result
   521  }
   522  
   523  // queryDocumentsOlderThan searches the Opensearch index with the fields in the given cluster
   524  func queryDocumentsOlderThan(index string, retentionPeriod string, kubeconfigPath string) (SearchResult, error) {
   525  	var result SearchResult
   526  
   527  	// validate Retention period
   528  	_, err := CalculateSeconds(retentionPeriod)
   529  	if err != nil {
   530  		return result, err
   531  	}
   532  
   533  	query := "@timestamp:<now-" + retentionPeriod
   534  	url := fmt.Sprintf("%s/%s/_search?q=%s", getOpenSearchURL(kubeconfigPath), index, url2.QueryEscape(query))
   535  	username, password, err := getOpenSearchUsernamePassword(kubeconfigPath)
   536  	if err != nil {
   537  		return result, nil
   538  	}
   539  	resp, err := getOpenSearchWithBasicAuth(url, "", username, password, kubeconfigPath)
   540  	if err != nil {
   541  		Log(Error, fmt.Sprintf(queryErrorFormat, url, err))
   542  		return result, nil
   543  	}
   544  	if resp.StatusCode != http.StatusOK {
   545  		Log(Error, fmt.Sprintf(queryStatusFormat, url, resp.StatusCode))
   546  		return result, nil
   547  	}
   548  	Log(Debug, fmt.Sprintf("records: %s", resp.Body))
   549  	json.Unmarshal(resp.Body, &result)
   550  	return result, nil
   551  }
   552  
   553  // LogIndexFound confirms a named index can be found in Opensearch in the cluster specified in the environment
   554  func LogIndexFound(indexName string) bool {
   555  	kubeconfigPath, err := k8sutil.GetKubeConfigLocation()
   556  	if err != nil {
   557  		Log(Error, fmt.Sprintf(kubeconfigErrorFormat, err))
   558  		return false
   559  	}
   560  
   561  	return LogIndexFoundInCluster(indexName, kubeconfigPath)
   562  }
   563  
   564  // LogIndexFoundInCluster confirms a named index can be found in Opensearch on the given cluster
   565  func LogIndexFoundInCluster(indexName, kubeconfigPath string) bool {
   566  	Log(Info, fmt.Sprintf("Looking for log index %s in cluster with kubeconfig %s", indexName, kubeconfigPath))
   567  	for _, name := range listSystemOpenSearchIndices(kubeconfigPath) {
   568  		// If old index or data stream backend index, return true
   569  		backendIndexRe := regexp.MustCompile(`^\.ds-` + indexName + `-\d+$`)
   570  		if name == indexName || backendIndexRe.MatchString(name) {
   571  			return true
   572  		}
   573  	}
   574  	Log(Error, fmt.Sprintf("Expected to find log index %s", indexName))
   575  	return false
   576  }
   577  
   578  // GetSystemIndices returns metadata of indices of all system indices
   579  func GetSystemIndices() ([]IndexMetadata, error) {
   580  	systemIndices, err := GetIndexMetadataList(ListSystemIndices())
   581  	if err != nil {
   582  		return []IndexMetadata{}, err
   583  	}
   584  	return systemIndices, nil
   585  }
   586  
   587  // GetApplicationIndices returns the metadata of indices used by application indices
   588  func GetApplicationIndices() ([]IndexMetadata, error) {
   589  	applicationIndices, err := GetIndexMetadataList(ListApplicationIndices())
   590  	if err != nil {
   591  		return []IndexMetadata{}, err
   592  	}
   593  	return applicationIndices, nil
   594  }
   595  
   596  // GetBackingIndicesForDataStream returns metadata of all backing indices for a given data stream
   597  func GetBackingIndicesForDataStream(dataStreamName string) ([]IndexMetadata, error) {
   598  	dataStream, err := GetDataStream(dataStreamName)
   599  	if err != nil {
   600  		return []IndexMetadata{}, err
   601  	}
   602  	var indexMetadataList []IndexMetadata
   603  	for _, index := range dataStream.Indices {
   604  		indexMetadata, err := GetIndexMetadata(index.IndexName)
   605  		if err != nil {
   606  			return indexMetadataList, err
   607  		}
   608  		indexMetadataList = append(indexMetadataList, indexMetadata)
   609  	}
   610  	return indexMetadataList, nil
   611  }
   612  
   613  // ContainsIndicesOlderThanRetentionPeriod returns true if there are any old (backing) indices present for
   614  // the given data stream that is older than the retention period. Returns false otherwise.
   615  func ContainsIndicesOlderThanRetentionPeriod(indexMetadataList []IndexMetadata, oldestTimestamp int64) (bool, error) {
   616  	for _, indexMetadata := range indexMetadataList {
   617  		Log(Info, fmt.Sprintf("Checking if creation time of index %s is older than %d", indexMetadata.ProvidedName, oldestTimestamp))
   618  		indexCreationTime, _ := strconv.ParseInt(indexMetadata.CreationDate, 10, 64)
   619  		Log(Info, fmt.Sprintf("Creation time of index '%s' is '%d'", indexMetadata.ProvidedName, indexCreationTime))
   620  		if indexCreationTime < oldestTimestamp {
   621  			return true, nil
   622  		}
   623  	}
   624  	return false, nil
   625  }
   626  
   627  // GetDataStream return the data stream object with the given
   628  func GetDataStream(dataStreamName string) (DataStream, error) {
   629  	var dataStream DataStream
   630  	resp, err := doGetOpenSearchURL(getDataStreamURLFormat + dataStreamName)
   631  	if err != nil {
   632  		return dataStream, err
   633  	}
   634  	if resp.StatusCode == http.StatusOK {
   635  		var dataStreamMap map[string][]DataStream
   636  		json.Unmarshal(resp.Body, &dataStreamMap)
   637  		dataStreams := dataStreamMap["data_streams"]
   638  		if len(dataStreams) > 0 {
   639  			// since the data stream object is queried using its name which is unique,
   640  			// atmost one element would be present in this splice
   641  			dataStream = dataStreams[0]
   642  		}
   643  	}
   644  	return dataStream, nil
   645  }
   646  
   647  // IsDataStreamSupported returns true if data stream is supported false otherwise
   648  func IsDataStreamSupported() bool {
   649  	resp, err := doGetOpenSearchURL(listDataStreamURLFormat)
   650  	if err != nil {
   651  		Log(Error, err.Error())
   652  		return false
   653  	}
   654  	if resp.StatusCode == http.StatusOK {
   655  		var dataStreamMap map[string][]DataStream
   656  		json.Unmarshal(resp.Body, &dataStreamMap)
   657  		dataStreams := dataStreamMap["data_streams"]
   658  		if len(dataStreams) > 0 {
   659  			return true
   660  		}
   661  	}
   662  	Log(Error, "No data streams created")
   663  	return false
   664  }
   665  
   666  // WaitForISMPolicyUpdate waits for the VMO reconcile to complete and the ISM policies are created
   667  func WaitForISMPolicyUpdate(pollingInterval time.Duration, timeout time.Duration) {
   668  	gomega.Eventually(func() bool {
   669  		ismPolicyExists, err := ISMPolicyExists(ApplicationLogIsmPolicyName)
   670  		if err != nil {
   671  			Log(Error, err.Error())
   672  			return false
   673  		}
   674  		return ismPolicyExists
   675  	}).WithPolling(pollingInterval).WithTimeout(timeout).Should(gomega.BeTrue())
   676  }
   677  
   678  func ListSystemIndices() []string {
   679  	return []string{
   680  		"verrazzano-namespace-cert-manager",
   681  		"verrazzano-namespace-verrazzano-system",
   682  		"verrazzano-namespace-local-path-storage",
   683  		"verrazzano-namespace-kube-system",
   684  		"verrazzano-namespace-cattle-fleet-local-system",
   685  		"verrazzano-namespace-ingress-nginx",
   686  		"verrazzano-systemd-journal",
   687  		"verrazzano-namespace-cattle-fleet-system",
   688  		"verrazzano-namespace-istio-system",
   689  		"verrazzano-namespace-monitoring",
   690  		"verrazzano-namespace-cattle-system",
   691  		"verrazzano-namespace-verrazzano-install",
   692  	}
   693  }
   694  
   695  func ListApplicationIndices() []string {
   696  	var indexList []string
   697  	resp, err := doGetOpenSearchURL("%s/_cat/indices?format=json")
   698  	if err != nil {
   699  		return indexList
   700  	}
   701  	if resp.StatusCode == http.StatusOK {
   702  		var indexListData []IndexListData
   703  		json.Unmarshal(resp.Body, &indexListData)
   704  		for _, indexData := range indexListData {
   705  			if !isSystemIndex(indexData.Index) {
   706  				indexList = append(indexList, indexData.Index)
   707  			}
   708  		}
   709  	}
   710  	return indexList
   711  }
   712  
   713  func GetIndexMetadataList(indexNames []string) ([]IndexMetadata, error) {
   714  	var indexMetadataList []IndexMetadata
   715  	for _, systemIndex := range indexNames {
   716  		systemIndexMetadata, err := GetIndexMetadata(systemIndex)
   717  		if err != nil {
   718  			return indexMetadataList, err
   719  		}
   720  		indexMetadataList = append(indexMetadataList, systemIndexMetadata)
   721  	}
   722  	return indexMetadataList, nil
   723  }
   724  
   725  // isSystemIndex returns true if the given index is a verrazzano system index false otherwise
   726  func isSystemIndex(indexName string) bool {
   727  	if strings.HasPrefix(indexName, ".") {
   728  		return true
   729  	}
   730  	for _, systemIndex := range ListSystemIndices() {
   731  		if systemIndex == indexName {
   732  			return true
   733  		}
   734  	}
   735  	return false
   736  }
   737  
   738  // doGetOpenSearchURL helper method to execute a GET request to open search url and return the response
   739  func doGetOpenSearchURL(urlFormat string) (*HTTPResponse, error) {
   740  	kubeconfigPath, err := k8sutil.GetKubeConfigLocation()
   741  	if err != nil {
   742  		return nil, err
   743  	}
   744  	url := fmt.Sprintf(urlFormat, getOpenSearchURL(kubeconfigPath))
   745  	username, password, err := getOpenSearchUsernamePassword(kubeconfigPath)
   746  	if err != nil {
   747  		return nil, err
   748  	}
   749  	return getOpenSearchWithBasicAuth(url, "", username, password, kubeconfigPath)
   750  }
   751  
   752  // GetApplicationDataStreamNames returns the data stream names of all application logs having
   753  // prefix 'verrazzano-application-'
   754  func GetApplicationDataStreamNames() ([]string, error) {
   755  	result := []string{}
   756  	resp, err := doGetOpenSearchURL("%s/_data_stream")
   757  	if err != nil {
   758  		return result, err
   759  	}
   760  	if resp.StatusCode == http.StatusOK {
   761  		var dataStreams map[string][]DataStream
   762  		json.Unmarshal(resp.Body, &dataStreams)
   763  		for _, dataStream := range dataStreams["data_streams"] {
   764  			if strings.HasPrefix(dataStream.Name, "verrazzano-application-") {
   765  				result = append(result, dataStream.Name)
   766  			}
   767  		}
   768  	}
   769  	return result, nil
   770  }
   771  
   772  // DeleteApplicationDataStream deletes the given applicatoin data stream
   773  func DeleteApplicationDataStream(datastreamName string) error {
   774  	kubeconfigPath, err := k8sutil.GetKubeConfigLocation()
   775  	if err != nil {
   776  		return err
   777  	}
   778  	username, password, err := getOpenSearchUsernamePassword(kubeconfigPath)
   779  	if err != nil {
   780  		return err
   781  	}
   782  	url := fmt.Sprintf(deleteDataStreamURLFormat, getOpenSearchURL(kubeconfigPath), datastreamName)
   783  	resp, err := deleteOpenSearchWithBasicAuth(url, "", username, password, kubeconfigPath)
   784  	if err != nil {
   785  		return err
   786  	}
   787  	if resp.StatusCode == http.StatusOK {
   788  		return nil
   789  	}
   790  	return nil
   791  }
   792  
   793  // GetIndexMetadata returns the metadata of the index
   794  func GetIndexMetadata(indexName string) (IndexMetadata, error) {
   795  	result := IndexMetadata{}
   796  	resp, err := doGetOpenSearchURL("%s/" + indexName + "/_settings")
   797  	if err != nil {
   798  		return result, err
   799  	}
   800  	if resp.StatusCode == http.StatusOK {
   801  		var settings map[string]IndexSettings
   802  		json.Unmarshal(resp.Body, &settings)
   803  		return settings[indexName].Settings.Index, nil
   804  	}
   805  	return result, nil
   806  }
   807  
   808  // GetIndexMetadataForDataStream returns the metadata of all backing indices of a given
   809  // datastream
   810  func GetIndexMetadataForDataStream(dataStreamName string) ([]IndexMetadata, error) {
   811  	result := []IndexMetadata{}
   812  	resp, err := doGetOpenSearchURL("%s/" + dataStreamName + "/_settings")
   813  	if err != nil {
   814  		return result, err
   815  	}
   816  	if resp.StatusCode == http.StatusOK {
   817  		var settings map[string]IndexSettings
   818  		json.Unmarshal(resp.Body, &settings)
   819  		for _, indexSettings := range settings {
   820  			result = append(result, indexSettings.Settings.Index)
   821  		}
   822  	}
   823  	return result, nil
   824  }
   825  
   826  // LogRecordFound confirms a recent log record for the index with matching fields can be found
   827  // in the cluster specified in the environment
   828  func LogRecordFound(indexName string, after time.Time, fields map[string]string) bool {
   829  	kubeconfigPath, err := k8sutil.GetKubeConfigLocation()
   830  	if err != nil {
   831  		Log(Error, fmt.Sprintf(kubeconfigErrorFormat, err))
   832  		return false
   833  	}
   834  
   835  	return LogRecordFoundInCluster(indexName, after, fields, kubeconfigPath)
   836  }
   837  
   838  // LogRecordFoundInCluster confirms a recent log record for the index with matching fields can be found
   839  // in the given cluster
   840  func LogRecordFoundInCluster(indexName string, after time.Time, fields map[string]string, kubeconfigPath string) bool {
   841  	searchResult := querySystemOpenSearch(indexName, fields, kubeconfigPath, true)
   842  	if len(searchResult) == 0 {
   843  		Log(Info, fmt.Sprintf("Expected to find log record matching fields %v", fields))
   844  		return false
   845  	}
   846  	found := findHits(searchResult, &after)
   847  	if !found {
   848  		Log(Error, fmt.Sprintf("Failed to find recent log record for index %s", indexName))
   849  	}
   850  	return found
   851  }
   852  
   853  // ContainsDocsOlderThanRetentionPeriod returns true if the given index contains any doc that
   854  // is older than the retention period, returns false otherwise.
   855  func ContainsDocsOlderThanRetentionPeriod(indexName string, retentionPeriod string) (bool, error) {
   856  	kubeconfigPath, err := k8sutil.GetKubeConfigLocation()
   857  	if err != nil {
   858  		Log(Error, fmt.Sprintf(kubeconfigErrorFormat, err))
   859  		return false, err
   860  	}
   861  	oldRecordsSearchResult, err := queryDocumentsOlderThan(indexName, retentionPeriod, kubeconfigPath)
   862  	if err != nil {
   863  		return false, err
   864  	}
   865  	return oldRecordsSearchResult.Hits.Total.Value > 0, nil
   866  }
   867  
   868  // findHits returns the number of hits that match a given search query
   869  func findHits(searchResult map[string]interface{}, after *time.Time) bool {
   870  	hits := Jq(searchResult, "hits", "hits")
   871  	if hits == nil {
   872  		Log(Info, "Expected to find hits in log record query results")
   873  		return false
   874  	}
   875  	Log(Info, fmt.Sprintf("Found %d records", len(hits.([]interface{}))))
   876  	if len(hits.([]interface{})) == 0 {
   877  		Log(Info, "Expected log record query results to contain at least one hit")
   878  		return false
   879  	}
   880  	if after == nil {
   881  		return true
   882  	}
   883  	for _, hit := range hits.([]interface{}) {
   884  		timestamp := Jq(hit, "_source", "@timestamp")
   885  		t, err := time.Parse(ISO8601Layout, timestamp.(string))
   886  		if err != nil {
   887  			t, err = time.Parse(time.RFC3339Nano, timestamp.(string))
   888  			if err != nil {
   889  				Log(Error, fmt.Sprintf("Failed to parse timestamp: %s", timestamp))
   890  				return false
   891  			}
   892  		}
   893  		if t.After(*after) {
   894  			Log(Info, fmt.Sprintf("Found recent record: %s", timestamp))
   895  			return true
   896  		}
   897  		Log(Info, fmt.Sprintf("Found old record: %s", timestamp))
   898  	}
   899  	return false
   900  }
   901  
   902  // OpensearchHit is the type used for a Opensearch hit returned in a search query result
   903  type OpensearchHit map[string]interface{}
   904  
   905  // OpensearchHitValidator is a function that validates a hit returned in a search query result
   906  type OpensearchHitValidator func(hit OpensearchHit) bool
   907  
   908  // ValidateOpensearchHits invokes the HitValidator on every hit in the searchResults.
   909  // The first invalid hit found will return in false being returned.
   910  // Otherwise true will be returned.
   911  func ValidateOpensearchHits(searchResults map[string]interface{}, hitValidator OpensearchHitValidator, exceptions []*regexp.Regexp) bool {
   912  	hits := Jq(searchResults, "hits", "hits")
   913  	if hits == nil {
   914  		Log(Info, "Expected to find hits in log record query results")
   915  		return false
   916  	}
   917  	Log(Info, fmt.Sprintf("Found %d records", len(hits.([]interface{}))))
   918  	if len(hits.([]interface{})) == 0 {
   919  		Log(Info, "Expected log record query results to contain at least one hit")
   920  		return false
   921  	}
   922  	valid := true
   923  	for _, h := range hits.([]interface{}) {
   924  		hit := h.(map[string]interface{})
   925  		src := hit["_source"].(map[string]interface{})
   926  		log := src["log"].(string)
   927  		if isException(log, exceptions) {
   928  			Log(Debug, fmt.Sprintf("Exception: %s", log))
   929  		} else {
   930  			if !hitValidator(src) {
   931  				valid = false
   932  			}
   933  		}
   934  	}
   935  	return valid
   936  }
   937  
   938  func isException(log string, exceptions []*regexp.Regexp) bool {
   939  	for _, re := range exceptions {
   940  		if re.MatchString(log) {
   941  			return true
   942  		}
   943  	}
   944  	return false
   945  }
   946  
   947  // FindLog returns true if a recent log record can be found in the index with matching filters.
   948  func FindLog(index string, match []Match, mustNot []Match) bool {
   949  	after := time.Now().Add(-24 * time.Hour)
   950  	query := OpensearchQuery{
   951  		Filters: match,
   952  		MustNot: mustNot,
   953  	}
   954  	result := SearchLog(index, query)
   955  	found := findHits(result, &after)
   956  	if !found {
   957  		Log(Error, fmt.Sprintf("Failed to find recent log record for index %s", index))
   958  	}
   959  	return found
   960  }
   961  
   962  // FindAnyLog returns true if a log record of any time can be found in the index with matching filters.
   963  func FindAnyLog(index string, match []Match, mustNot []Match) bool {
   964  	query := OpensearchQuery{
   965  		Filters: match,
   966  		MustNot: mustNot,
   967  	}
   968  	result := SearchLog(index, query)
   969  	found := findHits(result, nil)
   970  	if !found {
   971  		Log(Error, fmt.Sprintf("Failed to find recent log record for index %s", index))
   972  	}
   973  	return found
   974  }
   975  
   976  const numberOfErrorsToLog = 5
   977  
   978  // NoLog returns true if no matched log record can be found in the index.
   979  func NoLog(index string, match []Match, mustNot []Match) bool {
   980  	query := OpensearchQuery{
   981  		Filters: match,
   982  		MustNot: mustNot,
   983  	}
   984  	result := SearchLog(index, query)
   985  	if len(result) == 0 {
   986  		return true
   987  	}
   988  	hits := Jq(result, "hits", "hits")
   989  	if hits == nil || len(hits.([]interface{})) == 0 {
   990  		return true
   991  	}
   992  	Log(Error, fmt.Sprintf("Found unexpected %d records", len(hits.([]interface{}))))
   993  	for i, hit := range hits.([]interface{}) {
   994  		if i < numberOfErrorsToLog {
   995  			Log(Error, fmt.Sprintf("Found unexpected log record: %v", hit))
   996  		} else {
   997  			break
   998  		}
   999  	}
  1000  	return false
  1001  }
  1002  
  1003  var opensearchQueryTemplate *template.Template
  1004  
  1005  // SearchLog search recent log records for the index with matching filters.
  1006  func SearchLog(index string, query OpensearchQuery) map[string]interface{} {
  1007  	kubeconfigPath, err := k8sutil.GetKubeConfigLocation()
  1008  	if err != nil {
  1009  		Log(Error, fmt.Sprintf(kubeconfigErrorFormat, err))
  1010  		return nil
  1011  	}
  1012  	if opensearchQueryTemplate == nil {
  1013  		temp, err := template.New("esQueryTemplate").Parse(queryTemplate)
  1014  		if err != nil {
  1015  			Log(Error, fmt.Sprintf("Error: %v", err))
  1016  		}
  1017  		opensearchQueryTemplate = temp
  1018  	}
  1019  	var buffer bytes.Buffer
  1020  	err = opensearchQueryTemplate.Execute(&buffer, query)
  1021  	if err != nil {
  1022  		Log(Error, fmt.Sprintf("Error: %v", err))
  1023  	}
  1024  	configPath, err := k8sutil.GetKubeConfigLocation()
  1025  	if err != nil {
  1026  		Log(Error, fmt.Sprintf(kubeconfigErrorFormat, err))
  1027  		return nil
  1028  	}
  1029  	var result map[string]interface{}
  1030  	url := fmt.Sprintf("%s/%s/_search", getOpenSearchURL(kubeconfigPath), index)
  1031  	username, password, err := getOpenSearchUsernamePassword(configPath)
  1032  	if err != nil {
  1033  		return result
  1034  	}
  1035  	Log(Debug, fmt.Sprintf("Search: %v \nQuery: \n%v", url, buffer.String()))
  1036  	resp, err := postOpenSearchWithBasicAuth(url, buffer.String(), username, password, configPath)
  1037  	if err != nil {
  1038  		Log(Error, fmt.Sprintf(queryErrorFormat, url, err))
  1039  		return result
  1040  	}
  1041  	if resp.StatusCode != http.StatusOK {
  1042  		Log(Error, fmt.Sprintf(queryStatusFormat, url, resp.StatusCode))
  1043  		return result
  1044  	}
  1045  	json.Unmarshal(resp.Body, &result)
  1046  	return result
  1047  }
  1048  
  1049  // PostOpensearch POST the request entity body to Opensearch API path
  1050  // The provided path is appended to the Opensearch base URL
  1051  func PostOpensearch(path string, body string) (*HTTPResponse, error) {
  1052  	kubeconfigPath, err := k8sutil.GetKubeConfigLocation()
  1053  	if err != nil {
  1054  		Log(Error, fmt.Sprintf(kubeconfigErrorFormat, err))
  1055  		return nil, err
  1056  	}
  1057  	url := fmt.Sprintf("%s/%s", getOpenSearchURL(kubeconfigPath), path)
  1058  	configPath, err := k8sutil.GetKubeConfigLocation()
  1059  	if err != nil {
  1060  		Log(Error, fmt.Sprintf(kubeconfigErrorFormat, err))
  1061  		return nil, err
  1062  	}
  1063  	username, password, err := getOpenSearchUsernamePassword(configPath)
  1064  	if err != nil {
  1065  		return nil, err
  1066  	}
  1067  	Log(Debug, fmt.Sprintf("REST API path: %v \nQuery: \n%v", url, body))
  1068  	resp, err := postOpenSearchWithBasicAuth(url, body, username, password, configPath)
  1069  	return resp, err
  1070  }
  1071  
  1072  func IndicesNotExists(patterns []string) bool {
  1073  	kubeconfigPath, err := k8sutil.GetKubeConfigLocation()
  1074  	if err != nil {
  1075  		Log(Error, fmt.Sprintf(kubeconfigErrorFormat, err))
  1076  		return false
  1077  	}
  1078  	Log(Debug, fmt.Sprintf("Looking for indices in cluster using kubeconfig %s", kubeconfigPath))
  1079  	for _, name := range listSystemOpenSearchIndices(kubeconfigPath) {
  1080  		for _, pattern := range patterns {
  1081  			matched, _ := regexp.MatchString(pattern, name)
  1082  			if matched {
  1083  				Log(Error, fmt.Sprintf("Index %s matching the pattern %s still exists", name, pattern))
  1084  				return false
  1085  			}
  1086  		}
  1087  	}
  1088  	return true
  1089  }
  1090  
  1091  func ISMPolicyExists(policyName string) (bool, error) {
  1092  	kubeconfigPath, err := k8sutil.GetKubeConfigLocation()
  1093  	if err != nil {
  1094  		return false, err
  1095  	}
  1096  	username, password, err := getOpenSearchUsernamePassword(kubeconfigPath)
  1097  	if err != nil {
  1098  		return false, err
  1099  	}
  1100  	url := fmt.Sprintf("%s/_plugins/_ism/policies/%s", getOpenSearchURL(kubeconfigPath), policyName)
  1101  	resp, err := getOpenSearchWithBasicAuth(url, "", username, password, kubeconfigPath)
  1102  	if err != nil {
  1103  		return false, err
  1104  	}
  1105  	return resp.StatusCode == http.StatusOK, nil
  1106  }
  1107  
  1108  func GetISMPolicy(policyName string) (ISMPolicy, error) {
  1109  	result := ISMPolicy{}
  1110  	kubeconfigPath, err := k8sutil.GetKubeConfigLocation()
  1111  	if err != nil {
  1112  		return result, err
  1113  	}
  1114  	username, password, err := getOpenSearchUsernamePassword(kubeconfigPath)
  1115  	if err != nil {
  1116  		return result, err
  1117  	}
  1118  	url := fmt.Sprintf("%s/_plugins/_ism/policies/%s", getOpenSearchURL(kubeconfigPath), policyName)
  1119  	resp, err := getOpenSearchWithBasicAuth(url, "", username, password, kubeconfigPath)
  1120  	if err != nil {
  1121  		return result, err
  1122  	}
  1123  	if resp.StatusCode == http.StatusOK {
  1124  		var ismPolicyJSON map[string]ISMPolicy
  1125  		json.Unmarshal(resp.Body, &ismPolicyJSON)
  1126  		return ismPolicyJSON["policy"], nil
  1127  	}
  1128  	return result, nil
  1129  }
  1130  
  1131  func GetRetentionPeriod(policyName string) (string, error) {
  1132  	ismPolicy, err := GetISMPolicy(policyName)
  1133  	if err != nil {
  1134  		return "", err
  1135  	}
  1136  	for _, state := range ismPolicy.States {
  1137  		if state.Name == "ingest" {
  1138  			for _, transition := range state.Transitions {
  1139  				if transition.StateName == "delete" {
  1140  					minIndexAge := transition.Conditions["min_index_age"]
  1141  					return minIndexAge, nil
  1142  				}
  1143  			}
  1144  		}
  1145  	}
  1146  	return "", nil
  1147  }
  1148  
  1149  func GetISMRolloverPeriod(policyName string) (string, error) {
  1150  	ismPolicy, err := GetISMPolicy(policyName)
  1151  	if err != nil {
  1152  		return "", err
  1153  	}
  1154  	for _, state := range ismPolicy.States {
  1155  		if state.Name == "ingest" {
  1156  			for _, action := range state.Actions {
  1157  				rolloverPeriod := action.Rollover.MinIndexAge
  1158  				return rolloverPeriod, nil
  1159  			}
  1160  		}
  1161  	}
  1162  	return "", nil
  1163  }
  1164  
  1165  func CheckForDataStream(name string) bool {
  1166  	url := getDataStreamURLFormat + name
  1167  	resp, err := doGetOpenSearchURL(url)
  1168  	if err != nil {
  1169  		Log(Error, fmt.Sprintf("Error getting Opensearch data streams: url=%s, error=%v", url, err))
  1170  		return false
  1171  	}
  1172  	if resp.StatusCode != http.StatusOK {
  1173  		Log(Error, fmt.Sprintf("Error retrieving Opensearch data streams: url=%s, status=%d", url, resp.StatusCode))
  1174  		return false
  1175  	}
  1176  	return true
  1177  }
  1178  
  1179  // OpensearchQuery describes an Opensearch Query
  1180  type OpensearchQuery struct {
  1181  	Filters []Match
  1182  	MustNot []Match
  1183  }
  1184  
  1185  // Match describes a match_phrase in Opensearch Query
  1186  type Match struct {
  1187  	Key   string
  1188  	Value string
  1189  }
  1190  
  1191  const queryTemplate = `{
  1192    "size": 100,
  1193    "sort": [
  1194      {
  1195        "@timestamp": {
  1196          "order": "desc",
  1197          "unmapped_type": "boolean"
  1198        }
  1199      }
  1200    ],
  1201    "query": {
  1202      "bool": {
  1203        "filter": [
  1204          {
  1205            "match_all": {}
  1206          }
  1207  {{range $filter := .Filters}} 
  1208  		,
  1209          {
  1210            "match_phrase": {
  1211              "{{$filter.Key}}": "{{$filter.Value}}"
  1212            }
  1213          }
  1214  {{end}}
  1215        ],
  1216        "must_not": [
  1217  {{range $index, $mustNot := .MustNot}} 
  1218          {{if $index}},{{end}}
  1219          {
  1220            "match_phrase": {
  1221              "{{$mustNot.Key}}": "{{$mustNot.Value}}"
  1222            }
  1223          }
  1224  {{end}}
  1225        ]
  1226      }
  1227    }
  1228  }
  1229  `
  1230  
  1231  func PutISMPolicy(policyData, policyName string) (*HTTPResponse, error) {
  1232  	kubeconfigPath, err := k8sutil.GetKubeConfigLocation()
  1233  	if err != nil {
  1234  		return nil, err
  1235  	}
  1236  	username, password, err := getOpenSearchUsernamePassword(kubeconfigPath)
  1237  	if err != nil {
  1238  		return nil, err
  1239  	}
  1240  	policyURL := fmt.Sprintf("%s/_plugins/_ism/policies/%s", getOpenSearchURL(kubeconfigPath), policyName)
  1241  	resp, err := putOpenSearchWithBasicAuth(policyURL, policyData, username, password, kubeconfigPath)
  1242  	return resp, err
  1243  }
  1244  
  1245  func CheckISMPolicy() (bool, error) {
  1246  	result := false
  1247  	counter := 0
  1248  	kubeconfigPath, err := k8sutil.GetKubeConfigLocation()
  1249  	if err != nil {
  1250  		return false, err
  1251  	}
  1252  	username, password, err := getOpenSearchUsernamePassword(kubeconfigPath)
  1253  	if err != nil {
  1254  		return false, err
  1255  	}
  1256  	policyURL := fmt.Sprintf("%s/_plugins/_ism/policies/", getOpenSearchURL(kubeconfigPath))
  1257  	resp, err := getOpenSearchWithBasicAuth(policyURL, "", username, password, kubeconfigPath)
  1258  	if err != nil {
  1259  		return result, err
  1260  	}
  1261  	if resp.StatusCode == http.StatusOK {
  1262  		policies := &PolicyList{}
  1263  		err = json.Unmarshal(resp.Body, &policies)
  1264  		if err != nil {
  1265  			fmt.Println("error while processing", err)
  1266  		}
  1267  		for _, pl := range policies.Policies {
  1268  			for _, policy := range expectedSystemISMPolicies {
  1269  				if pl.Policy.PolicyID == policy {
  1270  					counter++
  1271  				}
  1272  			}
  1273  		}
  1274  		if counter == len(expectedSystemISMPolicies) {
  1275  			return true, nil
  1276  		}
  1277  	}
  1278  	return false, nil
  1279  }