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 }