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

     1  // Copyright (c) 2022, 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  	"context"
     8  	"encoding/json"
     9  	"fmt"
    10  	"time"
    11  
    12  	"github.com/verrazzano/verrazzano/pkg/k8sutil"
    13  
    14  	"net/http"
    15  	"strings"
    16  
    17  	"github.com/onsi/gomega"
    18  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    19  )
    20  
    21  const (
    22  	grafanaErrMsgFmt           = "Failed to GET Grafana testDashboard: status=%d: body=%s"
    23  	testDashboardTitle         = "E2ETestDashboard"
    24  	systemHealthDashboardTitle = "System Health"
    25  	openSearchMetricsDbTitle   = "OpenSearch Summary Dashboard"
    26  )
    27  
    28  type DashboardMetadata struct {
    29  	ID      int    `json:"id"`
    30  	Slug    string `json:"slug"`
    31  	Status  string `json:"status"`
    32  	UID     string `json:"uid"`
    33  	URL     string `json:"url"`
    34  	Version int    `json:"version"`
    35  }
    36  
    37  // CreateGrafanaDashboard creates a grafana dashboard using the JSON string provided
    38  func CreateGrafanaDashboard(body string) (*HTTPResponse, error) {
    39  	path := "api/dashboards/db"
    40  	kubeconfigPath, err := k8sutil.GetKubeConfigLocation()
    41  	if err != nil {
    42  		Log(Error, fmt.Sprintf(kubeconfigErrorFormat, err))
    43  		return nil, err
    44  	}
    45  	url := fmt.Sprintf("%s/%s", GetSystemGrafanaIngressURL(kubeconfigPath), path)
    46  	configPath, err := k8sutil.GetKubeConfigLocation()
    47  	if err != nil {
    48  		Log(Error, fmt.Sprintf(kubeconfigErrorFormat, err))
    49  		return nil, err
    50  	}
    51  	password, err := GetVerrazzanoPasswordInCluster(kubeconfigPath)
    52  	if err != nil {
    53  		return nil, err
    54  	}
    55  	Log(Debug, fmt.Sprintf("REST API path: %v \nQuery: \n%v", url, body))
    56  	resp, err := postGrafanaWithBasicAuth(url, body, "verrazzano", password, configPath)
    57  	return resp, err
    58  }
    59  
    60  // GetGrafanaDashboard returns the dashboard metadata for the given uid.
    61  func GetGrafanaDashboard(uid string) (*HTTPResponse, error) {
    62  	path := fmt.Sprintf("/api/dashboards/uid/%s", uid)
    63  	kubeconfigPath, err := k8sutil.GetKubeConfigLocation()
    64  	if err != nil {
    65  		Log(Error, fmt.Sprintf(kubeconfigErrorFormat, err))
    66  		return nil, err
    67  	}
    68  	url := fmt.Sprintf("%s/%s", GetSystemGrafanaIngressURL(kubeconfigPath), path)
    69  	configPath, err := k8sutil.GetKubeConfigLocation()
    70  	if err != nil {
    71  		Log(Error, fmt.Sprintf(kubeconfigErrorFormat, err))
    72  		return nil, err
    73  	}
    74  	password, err := GetVerrazzanoPasswordInCluster(kubeconfigPath)
    75  	if err != nil {
    76  		return nil, err
    77  	}
    78  	Log(Debug, fmt.Sprintf("REST API path: %s", url))
    79  	resp, err := getGrafanaWithBasicAuth(url, "", "verrazzano", password, configPath)
    80  	return resp, err
    81  }
    82  
    83  // SearchGrafanaDashboard returns the dashboard metadata for the given uid.
    84  func SearchGrafanaDashboard(searchParams map[string]string) (*HTTPResponse, error) {
    85  	queryParams := ""
    86  	for key, value := range searchParams {
    87  		queryParams += fmt.Sprintf("%s=%s", key, value)
    88  		queryParams += "&"
    89  	}
    90  	queryParams = strings.TrimSuffix(queryParams, "&")
    91  	path := fmt.Sprintf("/api/search?%s", queryParams)
    92  	kubeconfigPath, err := k8sutil.GetKubeConfigLocation()
    93  	if err != nil {
    94  		Log(Error, fmt.Sprintf(kubeconfigErrorFormat, err))
    95  		return nil, err
    96  	}
    97  	url := fmt.Sprintf("%s/%s", GetSystemGrafanaIngressURL(kubeconfigPath), path)
    98  	configPath, err := k8sutil.GetKubeConfigLocation()
    99  	if err != nil {
   100  		Log(Error, fmt.Sprintf(kubeconfigErrorFormat, err))
   101  		return nil, err
   102  	}
   103  	password, err := GetVerrazzanoPasswordInCluster(kubeconfigPath)
   104  	if err != nil {
   105  		return nil, err
   106  	}
   107  	Log(Debug, fmt.Sprintf("REST API path: %s", url))
   108  	resp, err := getGrafanaWithBasicAuth(url, "", "verrazzano", password, configPath)
   109  	return resp, err
   110  }
   111  
   112  // GetSystemGrafanaIngressURL gets the system Grafana Ingress host in the given cluster
   113  func GetSystemGrafanaIngressURL(kubeconfigPath string) string {
   114  	clientset, err := GetKubernetesClientsetForCluster(kubeconfigPath)
   115  	if err != nil {
   116  		Log(Error, fmt.Sprintf("Failed to get clientset for cluster %v", err))
   117  		return ""
   118  	}
   119  	ingressList, _ := clientset.NetworkingV1().Ingresses(VerrazzanoNamespace).List(context.TODO(), metav1.ListOptions{})
   120  	for _, ingress := range ingressList.Items {
   121  		if ingress.Name == "vmi-system-grafana" {
   122  			Log(Info, fmt.Sprintf("Found Opensearch Ingress %v, host %s", ingress.Name, ingress.Spec.Rules[0].Host))
   123  			return fmt.Sprintf("https://%s", ingress.Spec.Rules[0].Host)
   124  		}
   125  	}
   126  	return ""
   127  }
   128  
   129  // postGrafanaWithBasicAuth retries POST using basic auth
   130  func postGrafanaWithBasicAuth(url, body, username, password, kubeconfigPath string) (*HTTPResponse, error) {
   131  	retryableClient, err := GetVerrazzanoHTTPClient(kubeconfigPath)
   132  	if err != nil {
   133  		return nil, err
   134  	}
   135  	return doReq(url, "POST", "application/json", "", username, password, strings.NewReader(body), retryableClient)
   136  }
   137  
   138  // getGrafanaWithBasicAuth access Grafana with GET using basic auth, using a given kubeconfig
   139  func getGrafanaWithBasicAuth(url string, hostHeader string, username string, password string, kubeconfigPath string) (*HTTPResponse, error) {
   140  	retryableClient, err := GetVerrazzanoHTTPClient(kubeconfigPath)
   141  	if err != nil {
   142  		return nil, err
   143  	}
   144  	return doReq(url, "GET", "", hostHeader, username, password, nil, retryableClient)
   145  }
   146  
   147  func TestOpenSearchGrafanaDashBoard(pollingInterval time.Duration, timeout time.Duration) {
   148  	uid := "pIZicTl7z"
   149  	gomega.Eventually(func() bool {
   150  		resp, err := GetGrafanaDashboard(uid)
   151  		if err != nil {
   152  			Log(Error, err.Error())
   153  			return false
   154  		}
   155  		if resp.StatusCode != http.StatusOK {
   156  			Log(Error, fmt.Sprintf(grafanaErrMsgFmt, resp.StatusCode, string(resp.Body)))
   157  			return false
   158  		}
   159  		body := make(map[string]map[string]string)
   160  		json.Unmarshal(resp.Body, &body)
   161  		return strings.Contains(body["dashboard"]["title"], openSearchMetricsDbTitle)
   162  	}).WithPolling(pollingInterval).WithTimeout(timeout).Should(gomega.BeTrue())
   163  }
   164  
   165  func TestSystemHealthGrafanaDashboard(pollingInterval time.Duration, timeout time.Duration) {
   166  	// UID of system health dashboard, which is created by the VMO on startup.
   167  	uid := "Q4BkmcOVk"
   168  	gomega.Eventually(func() bool {
   169  		resp, err := GetGrafanaDashboard(uid)
   170  		if err != nil {
   171  			Log(Error, err.Error())
   172  			return false
   173  		}
   174  		if resp.StatusCode != http.StatusOK {
   175  			Log(Error, fmt.Sprintf(grafanaErrMsgFmt, resp.StatusCode, string(resp.Body)))
   176  			return false
   177  		}
   178  		body := make(map[string]map[string]string)
   179  		json.Unmarshal(resp.Body, &body)
   180  		return strings.Contains(body["dashboard"]["title"], systemHealthDashboardTitle)
   181  	}).WithPolling(pollingInterval).WithTimeout(timeout).Should(gomega.BeTrue())
   182  }
   183  
   184  func TestGrafanaTestDashboard(testDashboard DashboardMetadata, pollingInterval time.Duration, timeout time.Duration) {
   185  	gomega.Eventually(func() bool {
   186  		// UID of testDashboard, which is created by the previous test.
   187  		uid := testDashboard.UID
   188  		if uid == "" {
   189  			return false
   190  		}
   191  		resp, err := GetGrafanaDashboard(uid)
   192  		if err != nil {
   193  			Log(Error, err.Error())
   194  			return false
   195  		}
   196  		if resp.StatusCode != http.StatusOK {
   197  			Log(Error, fmt.Sprintf(grafanaErrMsgFmt, resp.StatusCode, string(resp.Body)))
   198  			return false
   199  		}
   200  		body := make(map[string]map[string]string)
   201  		json.Unmarshal(resp.Body, &body)
   202  		return strings.Contains(body["dashboard"]["title"], testDashboardTitle)
   203  	}).WithPolling(pollingInterval).WithTimeout(timeout).Should(gomega.BeTrue())
   204  }
   205  
   206  func TestSearchGrafanaDashboard(pollingInterval time.Duration, timeout time.Duration) {
   207  	gomega.Eventually(func() bool {
   208  		resp, err := SearchGrafanaDashboard(map[string]string{"query": testDashboardTitle})
   209  		if err != nil {
   210  			Log(Error, err.Error())
   211  			return false
   212  		}
   213  		if resp.StatusCode != http.StatusOK {
   214  			Log(Error, fmt.Sprintf(grafanaErrMsgFmt, resp.StatusCode, string(resp.Body)))
   215  			return false
   216  		}
   217  		var body []map[string]string
   218  		json.Unmarshal(resp.Body, &body)
   219  		for _, dashboard := range body {
   220  			if dashboard["title"] == "E2ETestDashboard" {
   221  				return true
   222  			}
   223  		}
   224  		return false
   225  
   226  	}).WithPolling(pollingInterval).WithTimeout(timeout).Should(gomega.BeTrue())
   227  }