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

     1  // Copyright (c) 2020, 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 vmi
     5  
     6  import (
     7  	"encoding/json"
     8  	"fmt"
     9  	"net/http"
    10  
    11  	"github.com/hashicorp/go-retryablehttp"
    12  	"github.com/verrazzano/verrazzano/pkg/httputil"
    13  	"github.com/verrazzano/verrazzano/pkg/k8sutil"
    14  	"github.com/verrazzano/verrazzano/tests/e2e/pkg"
    15  )
    16  
    17  // Opensearch contains information about the Opensearch instance
    18  type Opensearch struct {
    19  	ClusterName       string            `json:"cluster_name"`
    20  	EsVersion         OpensearchVersion `json:"version"`
    21  	binding           string
    22  	vmiHTTPClient     *retryablehttp.Client
    23  	OperatorManaged   bool
    24  	opensearchIngress string
    25  	osdIngress        string
    26  }
    27  
    28  // OpensearchVersion contains information about the version of Opensearch instance
    29  type OpensearchVersion struct {
    30  	Number       string `json:"number"`
    31  	Distribution string `json:"distribution"`
    32  }
    33  
    34  // GetOpensearch gets Opensearch representing the opensearch cluster with the binding name
    35  func GetOpensearch(binding string, operatorManaged bool) *Opensearch {
    36  	opensearchIngress := fmt.Sprintf("vmi-%v-os-ingest", binding)
    37  	osdIngress := fmt.Sprintf("vmi-%v-osd", binding)
    38  	if operatorManaged {
    39  		opensearchIngress = "opensearch"
    40  		osdIngress = "opensearch-dashboards"
    41  	}
    42  	return &Opensearch{
    43  		binding:           binding,
    44  		OperatorManaged:   operatorManaged,
    45  		opensearchIngress: opensearchIngress,
    46  		osdIngress:        osdIngress,
    47  	}
    48  }
    49  
    50  // PodsRunning checks if all opensearch required pods are running
    51  func (e *Opensearch) PodsRunning() bool {
    52  	expectedOpensearchPods := []string{
    53  		fmt.Sprintf("vmi-%s-es-master", e.binding),
    54  		fmt.Sprintf("vmi-%s-kibana", e.binding),
    55  		fmt.Sprintf("vmi-%s-grafana", e.binding),
    56  		fmt.Sprintf("vmi-%s-prometheus", e.binding),
    57  		fmt.Sprintf("vmi-%s-api", e.binding)}
    58  	running, _ := pkg.PodsRunning("verrazzano-system", expectedOpensearchPods)
    59  
    60  	if running {
    61  		expectedOpensearchPods = []string{
    62  			fmt.Sprintf("vmi-%s-es-ingest", e.binding),
    63  			fmt.Sprintf("vmi-%s-es-data", e.binding)}
    64  		running, _ = pkg.PodsRunning("verrazzano-system", expectedOpensearchPods)
    65  	}
    66  
    67  	return running
    68  }
    69  
    70  // getResponseBody gets the response body for the specified path from opensearch cluster
    71  func (e *Opensearch) getResponseBody(path string) ([]byte, error) {
    72  	kubeConfigPath, err := k8sutil.GetKubeConfigLocation()
    73  	if err != nil {
    74  		pkg.Log(pkg.Error, fmt.Sprintf("Error getting kubeconfig: %v", err))
    75  		return nil, err
    76  	}
    77  
    78  	api := pkg.EventuallyGetAPIEndpoint(kubeConfigPath)
    79  	esURL, err := api.GetOpensearchURL()
    80  	if err != nil {
    81  		pkg.Log(pkg.Error, fmt.Sprintf("Error getting Opensearch URL: %v", err))
    82  		return nil, err
    83  	}
    84  
    85  	esURL = esURL + path
    86  
    87  	password, err := pkg.GetVerrazzanoPasswordInCluster(kubeConfigPath)
    88  	if err != nil {
    89  		return nil, err
    90  	}
    91  
    92  	return e.retryGet(esURL, pkg.Username, password, kubeConfigPath)
    93  }
    94  
    95  // Connect checks if the Opensearch cluster can be connected
    96  func (e *Opensearch) Connect() bool {
    97  	body, err := e.getResponseBody("/")
    98  	if err != nil {
    99  		return false
   100  	}
   101  	err = json.Unmarshal(body, e)
   102  	return err == nil
   103  }
   104  
   105  func (e *Opensearch) retryGet(url, username, password string, kubeconfigPath string) ([]byte, error) {
   106  	req, _ := retryablehttp.NewRequest("GET", url, nil)
   107  	req.SetBasicAuth(username, password)
   108  	client, err := e.GetVmiHTTPClient(kubeconfigPath)
   109  	if err != nil {
   110  		pkg.Log(pkg.Info, fmt.Sprintf("Error getting HTTP client: %v", err))
   111  		return nil, err
   112  	}
   113  	client.CheckRetry = pkg.GetRetryPolicy()
   114  	resp, err := client.Do(req)
   115  	if err != nil {
   116  		pkg.Log(pkg.Info, fmt.Sprintf("Error GET %v error: %v", url, err))
   117  		return nil, err
   118  	}
   119  	if resp.StatusCode != 200 {
   120  		pkg.Log(pkg.Info, fmt.Sprintf("Response status code: %d", resp.StatusCode))
   121  	}
   122  	httpResp, err := pkg.ProcessHTTPResponse(resp)
   123  	if err != nil {
   124  		pkg.Log(pkg.Info, fmt.Sprintf("Error reading response from GET %v error: %v", url, err))
   125  		return nil, err
   126  	}
   127  	if httpResp.StatusCode == http.StatusNotFound {
   128  		err = fmt.Errorf("url %s returned not found", url)
   129  		pkg.Log(pkg.Info, fmt.Sprintf("NotFound %v error: %v", url, err))
   130  		return nil, err
   131  	}
   132  	return httpResp.Body, nil
   133  }
   134  
   135  func (e *Opensearch) GetOSIngressName() string {
   136  	return e.opensearchIngress
   137  }
   138  
   139  func (e *Opensearch) GetOSDIngressName() string {
   140  	return e.osdIngress
   141  }
   142  
   143  func (e *Opensearch) GetVmiHTTPClient(kubeconfigPath string) (*retryablehttp.Client, error) {
   144  	if e.vmiHTTPClient == nil {
   145  		var err error
   146  		e.vmiHTTPClient, err = pkg.GetVerrazzanoHTTPClient(kubeconfigPath)
   147  		if err != nil {
   148  			return nil, err
   149  		}
   150  	}
   151  	return e.vmiHTTPClient, nil
   152  }
   153  
   154  // ListIndices lists Opensearch indices
   155  func (e *Opensearch) ListIndices() []string {
   156  	idx := []string{}
   157  	for i := range e.getIndices() {
   158  		idx = append(idx, i)
   159  	}
   160  	return idx
   161  }
   162  
   163  // getIndices gets index metadata (aliases, mappings, and settings) of all Opensearch indices in the given cluster
   164  func (e *Opensearch) getIndices() map[string]interface{} {
   165  	body, err := e.getResponseBody("/_all")
   166  	if err != nil {
   167  		pkg.Log(pkg.Info, fmt.Sprintf("Error ListIndices error: %v", err))
   168  		return nil
   169  	}
   170  	var indices map[string]interface{}
   171  	json.Unmarshal(body, &indices)
   172  	return indices
   173  }
   174  
   175  // CheckTLSSecret checks the Opensearch secret
   176  func (e *Opensearch) CheckTLSSecret() bool {
   177  	secretName := fmt.Sprintf("%v-tls", e.binding)
   178  	return pkg.SecretsCreated("verrazzano-system", secretName)
   179  }
   180  
   181  // CheckHealth checks the health status of Opensearch cluster
   182  // Returns true if the health status is green otherwise false
   183  func (e *Opensearch) CheckHealth(kubeconfigPath string) bool {
   184  	supported, err := pkg.IsVerrazzanoMinVersion("1.1.0", kubeconfigPath)
   185  	if err != nil {
   186  		pkg.Log(pkg.Error, fmt.Sprintf("Error getting Verrazzano version: %v", err))
   187  		return false
   188  	}
   189  	if !supported {
   190  		pkg.Log(pkg.Info, "Skipping Elasticsearch cluster health check since version < 1.1.0")
   191  		return true
   192  	}
   193  	body, err := e.getResponseBody("/_cluster/health")
   194  	if err != nil {
   195  		pkg.Log(pkg.Error, fmt.Sprintf("Error getting cluster health: %v", err))
   196  		return false
   197  	}
   198  	pkg.Log(pkg.Info, fmt.Sprintf("Response body %v", string(body)))
   199  	status, err := httputil.ExtractFieldFromResponseBodyOrReturnError(string(body), "status", "unable to find status in Opensearch health response")
   200  	if err != nil {
   201  		pkg.Log(pkg.Error, fmt.Sprintf("Error extracting health status from response body: %v", err))
   202  		return false
   203  	}
   204  	indexTemplate, _ := e.getResponseBody("/_index_template")
   205  	pkg.Log(pkg.Info, fmt.Sprintf("IndexTemplate: %v", string(indexTemplate)))
   206  	catIndices, _ := e.getResponseBody("/_cat/indices")
   207  	pkg.Log(pkg.Info, fmt.Sprintf("Indices: \n%v", string(catIndices)))
   208  	if status == "green" {
   209  		pkg.Log(pkg.Info, "Opensearch cluster health status is green")
   210  		return true
   211  	} else if status == "yellow" { // Temporary WA for security audit log index as it gets created with 1 replica
   212  		pkg.Log(pkg.Info, "Opensearch cluster health status is yellow")
   213  		return true
   214  	}
   215  	pkg.Log(pkg.Error, fmt.Sprintf("Opensearch cluster health status is %v instead of green", status))
   216  	return false
   217  }
   218  
   219  // CheckIndicesHealth checks the health status of indices in a cluster
   220  // Returns true if the health status of all the indices is green otherwise false
   221  func (e *Opensearch) CheckIndicesHealth(kubeconfigPath string) bool {
   222  	supported, err := pkg.IsVerrazzanoMinVersion("1.1.0", kubeconfigPath)
   223  	if err != nil {
   224  		pkg.Log(pkg.Error, fmt.Sprintf("Error getting Verrazzano version: %v", err))
   225  		return false
   226  	}
   227  	if !supported {
   228  		pkg.Log(pkg.Info, "Skipping Elasticsearch indices health check since version < 1.1.0")
   229  		return true
   230  	}
   231  	body, err := e.getResponseBody("/_cat/indices?format=json")
   232  	if err != nil {
   233  		pkg.Log(pkg.Error, fmt.Sprintf("Error getting cluster indices: %v", err))
   234  		return false
   235  	}
   236  	pkg.Log(pkg.Info, fmt.Sprintf("Response body %v", string(body)))
   237  	var indices []map[string]interface{}
   238  	if err := json.Unmarshal(body, &indices); err != nil {
   239  		pkg.Log(pkg.Error, fmt.Sprintf("Error unmarshalling indices response body: %v", err))
   240  		return false
   241  	}
   242  
   243  	for _, index := range indices {
   244  		pkg.Log(pkg.Debug, fmt.Sprintf("Index details: %v", index))
   245  		val, found := index["health"]
   246  		if !found {
   247  			pkg.Log(pkg.Error, fmt.Sprintf("Not able to find the health of the index: %v", index))
   248  			return false
   249  		}
   250  		// Temporary WA for security audit log index as it gets created with 1 replica
   251  		if val.(string) == "red" {
   252  			pkg.Log(pkg.Error, fmt.Sprintf("Current index health status %v is not green or yellow", val))
   253  			return false
   254  		}
   255  	}
   256  	pkg.Log(pkg.Info, "The health status of all the indices is green or yellow")
   257  	return true
   258  }
   259  
   260  // //Check the Opensearch certificate
   261  // func (e *Opensearch) CheckCertificate() bool {
   262  //	certList, _ := pkg.ListCertificates("verrazzano-system")
   263  //	for _, cert := range certList.Items {
   264  //		if cert.Name == fmt.Sprintf("%v-tls", e.binding) {
   265  //			pkg.Log(pkg.Info, fmt.Sprintf("Found Certificate %v for binding %v", cert.Name, e.binding))
   266  //			for _, condition := range cert.Status.Conditions {
   267  //				if condition.Type == "Ready" {
   268  //					pkg.Log(pkg.Info, fmt.Sprintf("Certificate %v status: Ready = %v", cert.Name, condition.Status))
   269  //					return condition.Status == "True"
   270  //				}
   271  //			}
   272  //		}
   273  //	}
   274  //	return false
   275  // }
   276  
   277  // CheckIngress checks the Opensearch Ingress
   278  func (e *Opensearch) CheckIngress() bool {
   279  	ingressList, err := pkg.ListIngresses("verrazzano-system")
   280  	if err != nil {
   281  		pkg.Log(pkg.Error, fmt.Sprintf("Could not get list of ingresses: %v", err))
   282  		return false
   283  	}
   284  	for _, ingress := range ingressList.Items {
   285  		if ingress.Name == e.opensearchIngress {
   286  			pkg.Log(pkg.Info, fmt.Sprintf("Found Ingress %v for binding %v", ingress.Name, e.binding))
   287  			return true
   288  		}
   289  	}
   290  	return false
   291  }