github.com/verrazzano/verrazzano-monitoring-operator@v0.0.30/test/integ/basic_test.go (about) 1 // Copyright (C) 2020, 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 integ 5 6 import ( 7 "context" 8 "crypto/rand" 9 "crypto/tls" 10 "encoding/base64" 11 "encoding/json" 12 "fmt" 13 "io/ioutil" 14 "net/http" 15 "net/url" 16 "os" 17 "strings" 18 "testing" 19 "time" 20 21 "github.com/verrazzano/verrazzano-monitoring-operator/pkg/config" 22 "github.com/verrazzano/verrazzano-monitoring-operator/pkg/constants" 23 24 "github.com/verrazzano/verrazzano-monitoring-operator/pkg/resources" 25 26 "strconv" 27 28 vmcontrollerv1 "github.com/verrazzano/verrazzano-monitoring-operator/pkg/apis/vmcontroller/v1" 29 "github.com/verrazzano/verrazzano-monitoring-operator/test/integ/framework" 30 testutil "github.com/verrazzano/verrazzano-monitoring-operator/test/integ/util" 31 corev1 "k8s.io/api/core/v1" 32 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 33 ) 34 35 const ( 36 username = "vmo" 37 reporterUsername = "vmi-reporter" 38 changeUsername = "s@ur0n" 39 ) 40 41 var password = generateRandomString() 42 var reporterPassword = generateRandomString() 43 var changePassword = generateRandomString() 44 45 // ******************************************************** 46 // *** Scenarios covered by the Basic Integration Tests *** 47 // Setup 48 // - creation of a basic VMO instance + validations below 49 // - creation of a VMO instance with block volumes + validations below 50 // VMO API Server validations 51 // - Verify service endpoint connectivity 52 // Grafana Server validations 53 // - Verify service endpoint connectivity 54 // - Upload dashboard via Grafana HTTP API 55 // - GET/DELETE dashboard via Grafana HTTP API 56 // Elasticsearch Server validations 57 // - Verify service endpoint connectivity 58 // - Upload new document via Elasticsearch HTTP API 59 // - GET/DELETE document via Elasticsearch HTTP API 60 // Kibana Server validations 61 // - Verify service endpoint connectivity 62 // - Upload new document via Elasticsearch HTTP API 63 // - search for document via Kibana HTTP API 64 // - GET/DELETE entire index via Elasticsearch HTTP API 65 // ******************************************************** 66 67 func TestBasic1VMO(t *testing.T) { 68 f := framework.Global 69 var vmoName string 70 71 // Create secrets - domain only used with ingress 72 secretName := f.RunID + "-vmo-secrets" 73 testDomain := "ingress-test.example.com" 74 secret, err := createTestSecrets(secretName, testDomain) 75 if err != nil { 76 t.Errorf("failed to create test secrets: %+v", err) 77 } 78 79 // Create vmo 80 if f.Ingress { 81 vmoName = f.RunID + "-ingress" 82 } else { 83 vmoName = f.RunID + "-vmo-basic" 84 } 85 vmo := testutil.NewVMO(vmoName, secretName) 86 if f.Ingress { 87 vmo.Spec.URI = testDomain 88 } 89 90 if testutil.RunBeforePhase(f) { 91 // Create VMO instance 92 vmo, err = testutil.CreateVMO(f.CRClient, f.KubeClient2, f.Namespace, vmo, secret) 93 if err != nil { 94 t.Fatalf("Failed to create VMO: %v", err) 95 } 96 } else { 97 vmo, err = testutil.GetVMO(f.CRClient, f.Namespace, vmo) 98 if err != nil { 99 t.Fatalf("%v", err) 100 } 101 } 102 // Delete VMO instance if we are tearing down 103 if !testutil.SkipTeardown(f) { 104 defer func() { 105 if err := testutil.DeleteVMO(f.CRClient, f.KubeClient2, f.Namespace, vmo, secret); err != nil { 106 t.Fatalf("Failed to clean up VMO: %v", err) 107 } 108 }() 109 } 110 verifyVMODeployment(t, vmo) 111 } 112 113 func TestBasic2VMOWithDataVolumes(t *testing.T) { 114 f := framework.Global 115 116 // Create Secrets - domain only used with ingress 117 secretName := f.RunID + "-vmo-secrets" 118 testDomain := "ingress-test.example.com" 119 secret, err := createTestSecrets(secretName, testDomain) 120 if err != nil { 121 t.Errorf("failed to create test secrets: %+v", err) 122 } 123 124 // Create VMO 125 vmo := testutil.NewVMO(f.RunID+"-vmo-data", secretName) 126 vmo.Spec.Elasticsearch.Storage = vmcontrollerv1.Storage{Size: "50Gi"} 127 vmo.Spec.Grafana.Storage = vmcontrollerv1.Storage{Size: "50Gi"} 128 vmo.Spec.API.Replicas = 2 129 if f.Ingress { 130 vmo.Spec.URI = testDomain 131 } 132 133 if testutil.RunBeforePhase(f) { 134 // Create VMO instance 135 vmo, err = testutil.CreateVMO(f.CRClient, f.KubeClient2, f.Namespace, vmo, secret) 136 if err != nil { 137 t.Fatalf("Failed to create VMO: %v", err) 138 } 139 } else { 140 vmo, err = testutil.GetVMO(f.CRClient, f.Namespace, vmo) 141 if err != nil { 142 t.Fatalf("%v", err) 143 } 144 } 145 // Delete VMO instance if we are tearing down 146 if !testutil.SkipTeardown(f) { 147 defer func() { 148 if err := testutil.DeleteVMO(f.CRClient, f.KubeClient2, f.Namespace, vmo, secret); err != nil { 149 t.Fatalf("Failed to clean up VMO: %v", err) 150 } 151 }() 152 } 153 154 verifyVMODeployment(t, vmo) 155 } 156 157 func TestBasic3GrafanaOnlyVMOAPITokenOperations(t *testing.T) { 158 f := framework.Global 159 secretName := f.RunID + "-vmo-secrets" 160 secret := testutil.NewSecret(secretName, f.Namespace) 161 vmo := testutil.NewGrafanaOnlyVMO(f.RunID+"-"+"grafana-only", secretName) 162 var err error 163 if testutil.RunBeforePhase(f) { 164 // Create VMO instance 165 vmo, err = testutil.CreateVMO(f.CRClient, f.KubeClient2, f.Namespace, vmo, secret) 166 if err != nil { 167 t.Fatalf("Failed to create VMO: %v", err) 168 } 169 } else { 170 vmo, err = testutil.GetVMO(f.CRClient, f.Namespace, vmo) 171 if err != nil { 172 t.Fatalf("%v", err) 173 } 174 } 175 // Delete VMO instance if we are tearing down 176 if !testutil.SkipTeardown(f) { 177 defer func() { 178 if err := testutil.DeleteVMO(f.CRClient, f.KubeClient2, f.Namespace, vmo, secret); err != nil { 179 t.Fatalf("Failed to clean up VMO: %v", err) 180 } 181 }() 182 } 183 verifyGrafanaAPITokenOperations(t, vmo) 184 } 185 186 // Creates Simple vmo without canaries definied 187 // Use API server REST apis to create/update/delete canaries 188 func TestBasic4VMOMultiUserAuthn(t *testing.T) { 189 f := framework.Global 190 var err error 191 testDomain := "multiuser-authn.example.com" 192 193 hosts := "*." + testDomain + ",api." + testDomain + ",grafana." + testDomain + 194 ",kibana." + testDomain + ",elasticsearch." + testDomain + "," + f.ExternalIP 195 196 err = testutil.GenerateKeys(hosts, testDomain, "", 365*24*time.Hour, true, 2048, "P256") 197 if err != nil { 198 t.Fatalf("error generating keys: %+v", err) 199 } 200 tCert, err := ioutil.ReadFile(os.TempDir() + "/tls.crt") 201 if err != nil { 202 fmt.Print(err) 203 } 204 tKey, err := ioutil.ReadFile(os.TempDir() + "/tls.key") 205 if err != nil { 206 fmt.Print(err) 207 } 208 209 secretName := f.RunID + "-vmo-secrets" 210 extraCreds := []string{reporterUsername, reporterPassword} 211 secret := testutil.NewSecretWithTLSWithMultiUser(secretName, f.Namespace, tCert, tKey, username, password, extraCreds) 212 213 // Create VMO 214 vmo := testutil.NewVMO(f.RunID+"-"+"multiuser-authn", secretName) 215 vmo.Spec.URI = testDomain 216 217 if testutil.RunBeforePhase(f) { 218 // Create VMO instance 219 vmo, err = testutil.CreateVMO(f.CRClient, f.KubeClient2, f.Namespace, vmo, secret) 220 if err != nil { 221 t.Fatalf("Failed to create VMO: %v", err) 222 } 223 } else { 224 vmo, err = testutil.GetVMO(f.CRClient, f.Namespace, vmo) 225 if err != nil { 226 t.Fatalf("%v", err) 227 } 228 } 229 // Delete VMO instance if we are tearing down 230 if !testutil.SkipTeardown(f) { 231 defer func() { 232 if err := testutil.DeleteVMO(f.CRClient, f.KubeClient2, f.Namespace, vmo, secret); err != nil { 233 t.Fatalf("Failed to clean up VMO: %v", err) 234 } 235 }() 236 } 237 verifyMultiUserAuthnOperations(t, vmo) 238 } 239 240 func TestBasic4VMOWithIngress(t *testing.T) { 241 f := framework.Global 242 243 testDomain := "ingress-test.example.com" 244 hosts := "*." + testDomain + ",api." + testDomain + ",grafana." + testDomain + 245 ",kibana." + testDomain + ",elasticsearch." + testDomain + "," + f.ExternalIP 246 247 err := testutil.GenerateKeys(hosts, testDomain, "", 365*24*time.Hour, true, 2048, "P256") 248 if err != nil { 249 t.Fatalf("error generating keys: %+v", err) 250 } 251 tCert, err := ioutil.ReadFile(os.TempDir() + "/tls.crt") 252 if err != nil { 253 fmt.Print(err) 254 } 255 tKey, err := ioutil.ReadFile(os.TempDir() + "/tls.key") 256 if err != nil { 257 fmt.Print(err) 258 } 259 260 secretName := f.RunID + "-vmo-secrets" 261 secret := testutil.NewSecretWithTLS(secretName, f.Namespace, tCert, tKey, username, password) 262 263 // Create VMO 264 vmo := testutil.NewVMO(f.RunID+"-ingress", secretName) 265 vmo.Spec.URI = testDomain 266 267 if testutil.RunBeforePhase(f) { 268 // Create VMO instance 269 vmo, err = testutil.CreateVMO(f.CRClient, f.KubeClient2, f.Namespace, vmo, secret) 270 if err != nil { 271 t.Fatalf("Failed to create VMO: %v", err) 272 } 273 fmt.Printf("Ingress VMOSpec: %v", vmo) 274 } else { 275 vmo, err = testutil.GetVMO(f.CRClient, f.Namespace, vmo) 276 if err != nil { 277 t.Fatalf("%v", err) 278 } 279 } 280 // Delete VMO instance if we are tearing down 281 if !testutil.SkipTeardown(f) { 282 defer func() { 283 if err := testutil.DeleteVMO(f.CRClient, f.KubeClient2, f.Namespace, vmo, secret); err != nil { 284 t.Fatalf("Failed to clean up VMO: %v", err) 285 } 286 }() 287 } 288 verifyVMODeploymentWithIngress(t, vmo, username, password) 289 290 //update top-level secret new username/password 291 fmt.Println("Updating vmo username/paswword") 292 secret, err = f.KubeClient2.CoreV1().Secrets(vmo.Namespace).Get(context.Background(), secretName, metav1.GetOptions{}) 293 if err != nil { 294 t.Fatalf("secret %s doesn't exists in namespace %s : %v", secretName, vmo.Namespace, err) 295 } 296 secret.Data["username"] = []byte(changeUsername) 297 secret.Data["password"] = []byte(changePassword) 298 secret, err = f.KubeClient2.CoreV1().Secrets(vmo.Namespace).Update(context.Background(), secret, metav1.UpdateOptions{}) 299 if err != nil { 300 t.Fatalf("Error when updating a secret %s, %v", secretName, err) 301 } 302 verifyVMODeploymentWithIngress(t, vmo, changeUsername, changePassword) 303 } 304 305 func TestBasic4VMOOperatorMetricsServer(t *testing.T) { 306 f := framework.Global 307 operatorSvcPort := getPortFromService(t, f.OperatorNamespace, "verrazzano-monitoring-operator") 308 if err := testutil.WaitForEndpointAvailable("verrazzano-monitoring-operator", f.ExternalIP, operatorSvcPort, "/metrics", http.StatusOK, testutil.DefaultRetry); err != nil { 309 t.Fatal(err) 310 } 311 } 312 313 // Create appropriate secrets file for the test 314 func createTestSecrets(secretName, testDomain string) (*corev1.Secret, error) { 315 f := framework.Global 316 317 if !f.Ingress { 318 // Create simple secret 319 return testutil.NewSecret(secretName, f.Namespace), nil 320 } 321 322 // Create TLS Secret 323 hosts := "*." + testDomain + ",api." + testDomain + ",grafana." + testDomain + 324 ",kibana." + testDomain + ",elasticsearch." + testDomain + "," + f.ExternalIP 325 326 err := testutil.GenerateKeys(hosts, testDomain, "", 365*24*time.Hour, true, 2048, "P256") 327 if err != nil { 328 return nil, err 329 } 330 tCert, err := ioutil.ReadFile(os.TempDir() + "/tls.crt") 331 if err != nil { 332 return nil, err 333 } 334 tKey, err := ioutil.ReadFile(os.TempDir() + "/tls.key") 335 if err != nil { 336 return nil, err 337 } 338 339 return testutil.NewSecretWithTLS(secretName, f.Namespace, tCert, tKey, username, password), nil 340 } 341 342 func verifyMultiUserAuthnOperations(t *testing.T, vmo *vmcontrollerv1.VerrazzanoMonitoringInstance) { 343 f := framework.Global 344 var httpProtocol, myURL, host string 345 var apiPort, esPort int32 346 var resp *http.Response 347 var err error 348 var headers = map[string]string{} 349 350 fmt.Println("======================================================") 351 fmt.Printf("Testing VMO %s components in namespace '%s'\n", vmo.Name, f.Namespace) 352 if f.Ingress { 353 fmt.Println("Mode: Testing via the Ingress Controller") 354 } else { 355 fmt.Println("This test can only run in ingress mode") 356 return 357 } 358 359 // What port should we use? 360 if f.Ingress { 361 httpProtocol = "https://" 362 apiPort = getPortFromService(t, f.OperatorNamespace, f.IngressControllerSvcName) 363 } else { 364 httpProtocol = "http://" 365 apiPort = getPortFromService(t, f.Namespace, constants.VMOServiceNamePrefix+vmo.Name+"-"+config.API.Name) 366 esPort = getPortFromService(t, f.Namespace, constants.VMOServiceNamePrefix+vmo.Name+"-"+config.ElasticsearchIngest.Name) 367 } 368 369 // Verify service endpoint connectivity 370 // Verify API availability 371 waitForEndpoint(t, vmo, "API", apiPort, "/healthcheck") 372 fmt.Println(" ==> Service endpoint is available") 373 374 // Test 1: Validate reporter use is able to push to elasticsearch 375 index := strings.ToLower("verifyElasticsearch") + f.RunID 376 docPath := "/" + index + "/_doc/1" 377 378 message := make(map[string]interface{}) 379 message["message"] = "verifyElasticsearch." + f.RunID 380 381 jsonPayload, marshalErr := json.Marshal(message) 382 if marshalErr != nil { 383 t.Fatal(marshalErr) 384 } 385 386 myURL = fmt.Sprintf("%s%s:%d%s", httpProtocol, f.ExternalIP, esPort, docPath) 387 host = "elasticsearch." + vmo.Spec.URI 388 headers["Content-Type"] = "application/json" 389 resp, _, err = sendRequestWithUserPassword("POST", myURL, host, false, headers, string(jsonPayload), reporterUsername, reporterPassword) 390 if err != nil { 391 t.Fatal(err) 392 } 393 if resp.StatusCode != http.StatusCreated { 394 t.Fatalf("Expected response code %d from POST but got %d: (%v)", http.StatusCreated, resp.StatusCode, resp) 395 } 396 fmt.Println(" ==> Document " + docPath + " created") 397 398 } 399 400 func verifyVMODeployment(t *testing.T, vmo *vmcontrollerv1.VerrazzanoMonitoringInstance) { 401 f := framework.Global 402 fmt.Println("======================================================") 403 fmt.Printf("Testing VMO %s components in namespace '%s'\n", vmo.Name, f.Namespace) 404 if f.Ingress { 405 fmt.Println("Mode: Testing via the Ingress Controller") 406 } else { 407 fmt.Println("Mode: Testing via NodePorts") 408 } 409 fmt.Println("======================================================") 410 411 // Verify deployments 412 fmt.Println("Step 1: Verify VMO instance deployments") 413 414 // Verify deployments 415 var deploymentNamesToReplicas = map[string]int32{ 416 constants.VMOServiceNamePrefix + vmo.Name + "-" + config.API.Name: vmo.Spec.API.Replicas, 417 constants.VMOServiceNamePrefix + vmo.Name + "-" + config.Grafana.Name: 1, 418 constants.VMOServiceNamePrefix + vmo.Name + "-" + config.Kibana.Name: vmo.Spec.Kibana.Replicas, 419 constants.VMOServiceNamePrefix + vmo.Name + "-" + config.ElasticsearchIngest.Name: vmo.Spec.Elasticsearch.IngestNode.Replicas, 420 constants.VMOServiceNamePrefix + vmo.Name + "-" + config.ElasticsearchMaster.Name: vmo.Spec.Elasticsearch.MasterNode.Replicas, 421 } 422 for i := 0; i < int(vmo.Spec.Elasticsearch.DataNode.Replicas); i++ { 423 deploymentNamesToReplicas[constants.VMOServiceNamePrefix+vmo.Name+"-"+config.ElasticsearchData.Name+"-"+strconv.Itoa(i)] = 1 424 } 425 426 statefulSetComponents := []string{} 427 428 statefulSetComponents = append(statefulSetComponents, constants.VMOServiceNamePrefix+vmo.Name+"-"+config.ElasticsearchMaster.Name) 429 for deploymentName := range deploymentNamesToReplicas { 430 var err error 431 if resources.SliceContains(statefulSetComponents, deploymentName) { 432 err = testutil.WaitForStatefulSetAvailable(f.Namespace, deploymentName, 433 deploymentNamesToReplicas[deploymentName], testutil.DefaultRetry, f.KubeClient) 434 } else { 435 err = testutil.WaitForDeploymentAvailable(f.Namespace, deploymentName, 436 deploymentNamesToReplicas[deploymentName], testutil.DefaultRetry, f.KubeClient) 437 } 438 439 if err != nil { 440 t.Fatal(err) 441 } 442 } 443 fmt.Println(" ==> All deployments are available") 444 445 // Run the tests 446 fmt.Println("Step 2: Verify API service") 447 448 verifyAPI(t, vmo) 449 fmt.Println("Step 3: Verify Grafana") 450 verifyGrafana(t, vmo) 451 fmt.Println("Step 4: Verify Elasticsearch") 452 verifyElasticsearch(t, vmo, true, false) 453 fmt.Println("Step 5: Verify Kibana") 454 verifyKibana(t, vmo) 455 456 fmt.Println("======================================================") 457 } 458 459 func verifyAPI(t *testing.T, vmo *vmcontrollerv1.VerrazzanoMonitoringInstance) { 460 f := framework.Global 461 var apiPort int32 462 463 // What ports should we use? 464 if f.Ingress { 465 apiPort = getPortFromService(t, f.OperatorNamespace, f.IngressControllerSvcName) 466 } else { 467 apiPort = getPortFromService(t, f.Namespace, constants.VMOServiceNamePrefix+vmo.Name+"-"+config.API.Name) 468 } 469 470 // Wait for API availability 471 waitForEndpoint(t, vmo, "API", apiPort, "/healthcheck") 472 fmt.Println(" ==> Service endpoint is available") 473 } 474 475 func verifyGrafanaAPITokenOperations(t *testing.T, vmo *vmcontrollerv1.VerrazzanoMonitoringInstance) { 476 f := framework.Global 477 478 if !vmo.Spec.Grafana.Enabled { 479 return 480 } 481 482 grafanaSvc, err := testutil.WaitForService(f.Namespace, constants.VMOServiceNamePrefix+vmo.Name+"-grafana", testutil.DefaultRetry, f.KubeClient) 483 if err != nil { 484 t.Fatal(err) 485 } 486 487 // Verify service endpoint connectivity 488 if err := testutil.WaitForEndpointAvailable("Grafana", f.ExternalIP, grafanaSvc.Spec.Ports[0].NodePort, "/api/health", http.StatusOK, testutil.DefaultRetry); err != nil { 489 t.Fatal(err) 490 } 491 492 fmt.Println(" ==> service endpoint available") 493 494 if testutil.RunBeforePhase(f) { 495 // Test1 - Accessing the grafana endpoint root URL using NGINX basic auth should return 200. 496 // This validates backward compatability, Users can continue to use VMO basic auth 497 fmt.Println("Dashboard API Token tests") 498 restyClient := testutil.GetClient() 499 resp, err := restyClient.R().SetBasicAuth(username, password). 500 Get(fmt.Sprintf("http://%s:%d%s", 501 f.ExternalIP, grafanaSvc.Spec.Ports[0].NodePort, "/")) 502 if err != nil { 503 t.Fatalf("Failed to get root URL for grafana endpoint %v", err) 504 } 505 if resp.StatusCode() != http.StatusOK { 506 t.Fatalf("Expected response code %d from GET on %s but got %d: (%v)", 507 http.StatusOK, "/", resp.StatusCode(), resp) 508 } 509 fmt.Println("Test1 - Accessing the grafana endpoint root URL using NGINX basic auth should return 200 - Passed") 510 511 // Test2: Get a Admin API Token via Grafana HTTP API 512 restyClient = testutil.GetClient() 513 resp2, err2 := restyClient.R().SetHeader("Content-Type", "application/json"). 514 SetBody(`{"name":"adminTestApiKey", "role": "Admin"}`).SetBasicAuth(username, password). 515 Post(fmt.Sprintf("http://%s:%d%s", 516 f.ExternalIP, grafanaSvc.Spec.Ports[0].NodePort, "/api/auth/keys")) 517 if err2 != nil { 518 t.Fatal(err2) 519 } 520 if resp2.StatusCode() != http.StatusOK { 521 t.Fatalf("Expected response code %d from POST on %s but got %d: (%v)", 522 http.StatusOK, "/api/auth/keys", resp2.StatusCode(), resp2) 523 } 524 var jsonResp map[string]string 525 if unmarshalErr := json.Unmarshal(resp2.Body(), &jsonResp); unmarshalErr != nil { 526 t.Fatal(unmarshalErr) 527 } 528 var adminToken = jsonResp["key"] 529 fmt.Println("Dashboard API admin token: " + adminToken) 530 531 if adminToken == "" { 532 t.Fatalf("Did not get a admin API token from grafan") 533 } 534 fmt.Println("Test2: Get a Admin API Token via Grafana HTTP API - PASSED") 535 536 // Test3: Get a View API Token via Grafana HTTP API 537 var jsonResp3 map[string]string 538 restyClient = testutil.GetClient() 539 resp3, err3 := restyClient.R().SetHeader("Content-Type", "application/json"). 540 SetBody(`{"name":"viewTestApiKey", "role": "Viewer"}`).SetBasicAuth(username, password). 541 Post(fmt.Sprintf("http://%s:%d%s", 542 f.ExternalIP, grafanaSvc.Spec.Ports[0].NodePort, "/api/auth/keys")) 543 if err3 != nil { 544 t.Fatal(err3) 545 } 546 if resp3.StatusCode() != http.StatusOK { 547 t.Fatalf("Expected response code %d from POST on %s but got %d: (%v)", 548 http.StatusOK, "/api/auth/keys", resp3.StatusCode(), resp3) 549 } 550 551 if unmarshalErr := json.Unmarshal(resp3.Body(), &jsonResp3); unmarshalErr != nil { 552 t.Fatal(unmarshalErr) 553 } 554 var viewAPIToken = jsonResp3["key"] 555 fmt.Println("Dashboard API view token: " + viewAPIToken) 556 557 if viewAPIToken == "" { 558 t.Fatalf("Did not get a view API token from grafana") 559 } 560 fmt.Println("Test3: Get a View API Token via Grafana HTTP API - PASSED") 561 562 //Test4: Validate that any Grafana API call fails if No Valid API Token is passed 563 restyClient = testutil.GetClient() 564 resp4, err4 := restyClient.R(). 565 Get(fmt.Sprintf("http://%s:%d%s", 566 f.ExternalIP, grafanaSvc.Spec.Ports[0].NodePort, "/api/org")) 567 if err4 != nil { 568 t.Fatal(err4) 569 } 570 if resp4.StatusCode() != http.StatusUnauthorized { 571 t.Fatalf("Expected response code %d from GET on %s but got %d: (%v)", 572 http.StatusUnauthorized, "/api/auth/keys", resp4.StatusCode(), resp4) 573 } 574 fmt.Println("Test4: Validate that any Grafana API call fails if No Valid API Token is passed - PASSED") 575 576 //Test5: Validate that View Only operation PASSES with a Viewer role API Token. 577 restyClient = testutil.GetClient() 578 resp5, err5 := restyClient.R().SetHeader("Authorization", "Bearer "+viewAPIToken). 579 Get(fmt.Sprintf("http://%s:%d%s", 580 f.ExternalIP, grafanaSvc.Spec.Ports[0].NodePort, "/api/org")) 581 if err5 != nil { 582 t.Fatal(err5) 583 } 584 if resp5.StatusCode() != http.StatusOK { 585 t.Fatalf("Expected response code %d from POST on %s but got %d: (%v)", 586 http.StatusOK, "/api/auth/keys", resp5.StatusCode(), resp5) 587 } 588 fmt.Printf("Test5: Validate that View Only operation PASSES with a Viewer role API Token. (%v) - PASSED\n", resp5) 589 590 //Test6: Validate that Admin Operation FAILS with a Viewer Role API Token 591 var jsonResp6 map[string]string 592 restyClient = testutil.GetClient() 593 resp6, err6 := restyClient.R().SetHeader("Content-Type", "application/json"). 594 SetHeader("Authorization", "Bearer "+viewAPIToken).SetBody(`{"name":"Main org."}`). 595 Put(fmt.Sprintf("http://%s:%d%s", 596 f.ExternalIP, grafanaSvc.Spec.Ports[0].NodePort, "/api/org")) 597 if err6 != nil { 598 t.Fatal(err2) 599 } 600 if resp6.StatusCode() != http.StatusForbidden { 601 t.Fatalf("Expected response code %d from POST on %s but got %d: (%v)", 602 http.StatusForbidden, "/api/auth/keys", resp6.StatusCode(), resp6) 603 } 604 605 if unmarshalErr := json.Unmarshal(resp6.Body(), &jsonResp6); unmarshalErr != nil { 606 t.Fatal(unmarshalErr) 607 } 608 var message = jsonResp6["message"] 609 fmt.Println("Admin operation with View Only Token message: " + message) 610 611 if message != "Permission denied" { 612 t.Fatalf("Did not get Permission Denied message on Admin Operation with Viewer API Token, got (%v)", resp6) 613 } 614 fmt.Println("Test6: Validate that Admin Operation FAILS with a Viewer Role API Token - PASSED") 615 616 //Test7: Validate that Admin Operation PASSES with ONLY a Admin Role API Token 617 var jsonResp7 map[string]string 618 restyClient = testutil.GetClient() 619 resp7, err7 := restyClient.R().SetHeader("Content-Type", "application/json"). 620 SetHeader("Authorization", "Bearer "+adminToken).SetBody(`{"name":"Main org."}`). 621 Put(fmt.Sprintf("http://%s:%d%s", 622 f.ExternalIP, grafanaSvc.Spec.Ports[0].NodePort, "/api/org")) 623 if err7 != nil { 624 t.Fatal(err7) 625 } 626 if resp7.StatusCode() != http.StatusOK { 627 t.Fatalf("Expected response code %d from POST on %s but got %d: (%v)", 628 http.StatusOK, "/api/auth/keys", resp7.StatusCode(), resp7) 629 } 630 631 if unmarshalErr := json.Unmarshal(resp7.Body(), &jsonResp7); unmarshalErr != nil { 632 t.Fatal(unmarshalErr) 633 } 634 message = jsonResp7["message"] 635 fmt.Println("Admin operation with Admin role Token message: " + message) 636 637 if message != "Organization updated" { 638 t.Fatalf("Did not get Organization updated message on Admin Operation with Admin API Token") 639 } 640 fmt.Println("Test7: Validate that Admin Operation PASSES with a Admin Role API Token - PASSED") 641 642 } 643 if testutil.RunAfterPhase(f) { 644 if !testutil.SkipTeardown(f) { 645 //Delete The API token keys created. 646 //Test 8: Delete the API Viewer Token 647 restyClient := testutil.GetClient() 648 resp8, err8 := restyClient.R().SetBasicAuth(username, password). 649 Delete(fmt.Sprintf("http://%s:%d%s", 650 f.ExternalIP, grafanaSvc.Spec.Ports[0].NodePort, "/api/auth/keys/1")) 651 if err8 != nil { 652 t.Fatal(err8) 653 } 654 if resp8.StatusCode() != http.StatusOK { 655 t.Fatalf("Expected response code %d from POST on %s but got %d: (%v)", 656 http.StatusOK, "/api/auth/keys", resp8.StatusCode(), resp8) 657 } 658 fmt.Println("Test8: Delete the API Viewer Token - PASSED") 659 660 //Test 9: Delete API Admin Token 661 restyClient = testutil.GetClient() 662 resp9, err9 := restyClient.R().SetBasicAuth(username, password). 663 Delete(fmt.Sprintf("http://%s:%d%s", 664 f.ExternalIP, grafanaSvc.Spec.Ports[0].NodePort, "/api/auth/keys/1")) 665 if err9 != nil { 666 t.Fatal(err9) 667 } 668 if resp9.StatusCode() != http.StatusOK { 669 t.Fatalf("Expected response code %d from POST on %s but got %d: (%v)", 670 http.StatusOK, "/api/auth/keys", resp9.StatusCode(), resp9) 671 } 672 fmt.Println("Test 9: Delete API Admin Token - PASSED") 673 674 } 675 } 676 } 677 678 func verifyGrafana(t *testing.T, vmo *vmcontrollerv1.VerrazzanoMonitoringInstance) { 679 f := framework.Global 680 var port int32 681 var httpProtocol, myURL, host, body string 682 var resp *http.Response 683 var err error 684 var headers = map[string]string{} 685 686 externalDomainName := "localhost" 687 if !vmo.Spec.Grafana.Enabled { 688 return 689 } 690 691 // What port should we use? 692 if f.Ingress { 693 port = getPortFromService(t, f.OperatorNamespace, f.IngressControllerSvcName) 694 httpProtocol = "https://" 695 externalDomainName = "grafana." + vmo.Spec.URI 696 } else { 697 port = getPortFromService(t, f.Namespace, constants.VMOServiceNamePrefix+vmo.Name+"-"+config.Grafana.Name) 698 httpProtocol = "http://" 699 } 700 701 // Verify Grafana availability 702 waitForEndpoint(t, vmo, "Grafana", port, "/api/health") 703 fmt.Println(" ==> Service endpoint is available") 704 705 /* Verify domain and root_url */ 706 host = "grafana." + vmo.Spec.URI 707 myURL = fmt.Sprintf("%s%s:%d%s", httpProtocol, f.ExternalIP, port, "/api/admin/settings") 708 resp, body, err = sendRequest("GET", myURL, host, true, headers, "") 709 710 fmt.Println("Checking for domain and root_url in Grafana config") 711 if err != nil { 712 t.Fatalf("Failed to retrieve Grafana settings %s %v", f.RunID, err) 713 } 714 if resp.StatusCode != http.StatusOK { 715 t.Fatalf("Expected response code %d from GET on %s but got %d: (%v)", 716 http.StatusOK, "/api/admin/settings", resp.StatusCode, resp) 717 } 718 719 var adminRes map[string]interface{} 720 if err := json.Unmarshal([]byte(body), &adminRes); err != nil { 721 t.Fatalf("Expected Grafana config in output but found a different result:\n %s", body) 722 } 723 724 if len(adminRes) == 0 { 725 t.Fatal("Expected a result in response but found none") 726 } 727 728 if _, ok := adminRes["server"]; !ok { 729 t.Fatalf("Expected \"server\" element in result but found none\n") 730 } 731 732 grafanaServerConfig := adminRes["server"].(map[string]interface{}) 733 if domain, ok := grafanaServerConfig["domain"]; !ok { 734 t.Fatalf("Expected 'domain' element in result but found none\n") 735 } else if domain != externalDomainName { 736 t.Fatalf("Actual domain value '%s' doesn't match expected value '%s'\n", domain, externalDomainName) 737 } else { 738 fmt.Printf("'domain' obtained from Grafana config is = %+v\n", domain) 739 } 740 if externalDomainName != "localhost" { 741 if rootURL, ok := grafanaServerConfig["root_url"]; !ok { 742 t.Fatalf("Expected 'root_url' element in result but found none\n") 743 } else if rootURL != "https://"+externalDomainName { 744 t.Fatalf("Actual root_url value '%s' doesn't match expected value '%s'\n", rootURL, "https://"+externalDomainName) 745 } else { 746 fmt.Printf("'root_url' obtained from Grafana config is = %+v\n", rootURL) 747 } 748 } 749 750 /** End verify domain and root_url **/ 751 752 dashboardConfig, err := readTestDashboardConfig(f.RunID) 753 if err != nil { 754 t.Fatal(err) 755 } 756 757 if testutil.RunBeforePhase(f) { 758 759 // Verify "vmo" user can retrieve the user list 760 // This is a good check that authentication is being passedi properly with ingress 761 myURL = fmt.Sprintf("%s%s:%d%s", httpProtocol, f.ExternalIP, port, "/api/users") 762 host = "grafana." + vmo.Spec.URI 763 resp, _, err = sendRequest("GET", myURL, host, true, headers, "") 764 if err != nil { 765 t.Fatal(err) 766 } 767 if resp.StatusCode != http.StatusOK { 768 t.Fatalf("Expected response code %d from GET on %s but got %d: (%v)", 769 http.StatusOK, "/api/users", resp.StatusCode, resp) 770 } 771 fmt.Println(" ==> List of users received") 772 773 // Upload a new dashboard via Grafana HTTP API 774 myURL = fmt.Sprintf("%s%s:%d%s", httpProtocol, f.ExternalIP, port, "/api/dashboards/db") 775 host = "grafana." + vmo.Spec.URI 776 headers["Content-Type"] = "application/json" 777 resp, _, err = sendRequest("POST", myURL, host, true, headers, dashboardConfig) 778 if err != nil { 779 t.Fatal(err) 780 } 781 if resp.StatusCode != http.StatusOK { 782 t.Fatalf("Expected response code %d from POST on %s but got %d: (%v)", 783 http.StatusOK, "/api/dashboards/db", resp.StatusCode, resp) 784 } 785 fmt.Println(" ==> Dashboard " + f.RunID + " created") 786 } 787 if testutil.RunAfterPhase(f) { 788 // GET dashboard via Grafana HTTP API 789 myURL = fmt.Sprintf("%s%s:%d%s", httpProtocol, f.ExternalIP, port, "/api/search?type=dash-db&query="+f.RunID) 790 host = "grafana." + vmo.Spec.URI 791 fmt.Printf("URL: %s\n", myURL) 792 resp, body, err = sendRequest("GET", myURL, host, true, headers, "") 793 794 if err != nil { 795 t.Fatalf("Failed to retrieve dashboard %s %v", f.RunID, err) 796 } 797 if resp.StatusCode != http.StatusOK { 798 t.Fatalf("Expected response code %d from GET on %s but got %d: (%v)", 799 http.StatusOK, "/api/search?type=dash-db&query="+f.RunID, resp.StatusCode, resp) 800 } 801 802 var res []map[string]interface{} 803 if err := json.Unmarshal([]byte(body), &res); err != nil { 804 t.Fatalf("Expected a list of dashboards but found a different result:\n %s", body) 805 } 806 807 if len(res) == 0 { 808 t.Fatal("Expected a result in response but found none") 809 } 810 811 dashUID := "" 812 for _, dash := range res { 813 if dash["title"] == f.RunID { 814 dashUID = dash["uid"].(string) 815 break 816 } 817 } 818 819 if dashUID == "" { 820 t.Fatalf("Expected a dashboard with title %s but found none\n", f.RunID) 821 } 822 fmt.Println(" ==> Dashboard " + f.RunID + " retrieved") 823 824 // DELETE dashboard via Grafana HTTP API if we are tearing down 825 if !testutil.SkipTeardown(f) { 826 myURL = fmt.Sprintf("%s%s:%d%s", httpProtocol, f.ExternalIP, port, "/api/dashboards/uid/"+dashUID) 827 host = "grafana." + vmo.Spec.URI 828 fmt.Printf("URL: %s\n", myURL) 829 resp, _, err = sendRequest("DELETE", myURL, host, true, headers, "") 830 if err != nil { 831 t.Fatal(err) 832 } 833 if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusAccepted { 834 t.Fatalf("Expected response code %d or %d from DELETE on %s but got %d: (%v)", http.StatusOK, http.StatusAccepted, "/api/dashboards/uid/"+dashUID, resp.StatusCode, resp) 835 } 836 fmt.Println(" ==> Dashboard " + f.RunID + " deleted") 837 } 838 } 839 } 840 841 func verifyElasticsearch(t *testing.T, vmo *vmcontrollerv1.VerrazzanoMonitoringInstance, verifyIngestPipeline bool, verifyVMOIndex bool) { 842 f := framework.Global 843 844 var httpProtocol, myURL, host string 845 var esPort int32 846 var resp *http.Response 847 var err error 848 var headers = map[string]string{} 849 850 if !vmo.Spec.Elasticsearch.Enabled { 851 return 852 } 853 854 index := strings.ToLower("verifyElasticsearch") + f.RunID 855 docPath := "/" + index + "/_doc/" 856 countPath := "/_cat/count/" + index 857 ingestPipelinePath := "/_ingest/pipeline/" + f.RunID 858 859 // What port should we use? 860 if f.Ingress { 861 esPort = getPortFromService(t, f.OperatorNamespace, f.IngressControllerSvcName) 862 httpProtocol = "https://" 863 } else { 864 esPort = getPortFromService(t, f.Namespace, constants.VMOServiceNamePrefix+vmo.Name+"-"+config.ElasticsearchIngest.Name) 865 httpProtocol = "http://" 866 } 867 host = "elasticsearch." + vmo.Spec.URI 868 869 // Verify service endpoint connectivity 870 waitForEndpoint(t, vmo, "Elasticsearch", esPort, "/_cluster/health") 871 waitForEndpoint(t, vmo, "Elasticsearch", esPort, "/_cat/indices") 872 fmt.Println(" ==> Service endpoint is available") 873 874 // Verify expected cluster size 875 expectedClusterSize := vmo.Spec.Elasticsearch.MasterNode.Replicas + vmo.Spec.Elasticsearch.IngestNode.Replicas + vmo.Spec.Elasticsearch.DataNode.Replicas 876 fmt.Printf(" ==> Verifying Elasticsearch cluster size is as expected (%d)\n", expectedClusterSize) 877 err = waitForKeyWithValueResponse(host, f.ExternalIP, esPort, "/_cluster/stats", "successful", strconv.Itoa(int(expectedClusterSize))) 878 if err != nil { 879 t.Fatal(err) 880 } 881 882 if testutil.RunBeforePhase(f) { 883 // Add a log record 884 message := make(map[string]interface{}) 885 message["message"] = "verifyElasticsearch." + f.RunID 886 887 jsonPayload, marshalErr := json.Marshal(message) 888 if marshalErr != nil { 889 t.Fatal(marshalErr) 890 } 891 for i := 1; i <= 100; i++ { 892 893 myURL = fmt.Sprintf("%s%s:%d%s%d", httpProtocol, f.ExternalIP, esPort, docPath, i) 894 headers["Content-Type"] = "application/json" 895 resp, _, err = sendRequest("POST", myURL, host, false, headers, string(jsonPayload)) 896 if err != nil { 897 t.Fatal(err) 898 } 899 if resp.StatusCode != http.StatusCreated { 900 t.Fatalf("Expected response code %d from POST but got %d: (%v)", http.StatusCreated, resp.StatusCode, resp) 901 } 902 } 903 fmt.Println(" ==> 100 Documents " + docPath + " created") 904 905 if verifyIngestPipeline { 906 // Add an ingest pipeline 907 jsonPayload = []byte(`{"processors": [{"rename": {"field": "hostname","target_field": "host", "ignore_missing": true}}]}`) 908 myURL = fmt.Sprintf("%s%s:%d%s", httpProtocol, f.ExternalIP, esPort, ingestPipelinePath) 909 resp, _, err = sendRequest("PUT", myURL, host, false, headers, string(jsonPayload)) 910 if err != nil { 911 t.Fatal(err) 912 } 913 if resp.StatusCode != http.StatusOK { 914 t.Fatalf("Expected response code %d from PUT but got %d: (%v)", http.StatusOK, resp.StatusCode, resp) 915 } 916 fmt.Println(" ==> IngestNodes Pipeline " + ingestPipelinePath + " created") 917 } 918 919 ElasticSearchService, err := testutil.WaitForService(f.Namespace, constants.VMOServiceNamePrefix+vmo.Name+"-"+config.ElasticsearchIngest.Name, testutil.DefaultRetry, f.KubeClient) 920 if err != nil { 921 t.Fatal(err) 922 } 923 924 ElasticSearchIndexDocCountURL := fmt.Sprintf("http://%s:%d%s", f.ExternalIP, ElasticSearchService.Spec.Ports[0].NodePort, countPath) 925 926 found, err := testutil.WaitForElasticSearchIndexDocCount(ElasticSearchIndexDocCountURL, "100", testutil.DefaultRetry) 927 if err != nil { 928 t.Fatal(err) 929 } 930 if !found { 931 t.Error("Docs in the index are not 100") 932 } 933 } 934 935 if testutil.RunAfterPhase(f) { 936 937 for i := 1; i <= 100; i++ { 938 // Verify log record 939 myURL = fmt.Sprintf("%s%s:%d%s%d", httpProtocol, f.ExternalIP, esPort, docPath, i) 940 resp, _, err = sendRequest("GET", myURL, host, false, headers, "") 941 if err != nil { 942 t.Fatalf("Failed to retrieve document %v", err) 943 } 944 if resp.StatusCode != http.StatusOK { 945 t.Fatalf("Expected response code %d from GET but got %d: (%v)", http.StatusOK, resp.StatusCode, resp) 946 } 947 } 948 fmt.Println(" ==> 100 Documents " + docPath + " retrieved") 949 950 //Verify Doc Count 951 ElasticSearchService, err := testutil.WaitForService(f.Namespace, constants.VMOServiceNamePrefix+vmo.Name+"-"+config.ElasticsearchIngest.Name, testutil.DefaultRetry, f.KubeClient) 952 if err != nil { 953 t.Fatal(err) 954 } 955 ElasticSearchIndexDocCountURL := fmt.Sprintf("http://%s:%d%s", f.ExternalIP, ElasticSearchService.Spec.Ports[0].NodePort, countPath) 956 957 found, err := testutil.WaitForElasticSearchIndexDocCount(ElasticSearchIndexDocCountURL, "100", testutil.DefaultRetry) 958 if err != nil { 959 t.Fatal(err) 960 } 961 if !found { 962 t.Error("Docs in the restored index are not 100") 963 } 964 965 // Hack for upgrade from ES 6.x to 7.x 966 if !strings.HasPrefix(f.ElasticsearchVersion, "7.") { 967 if verifyIngestPipeline { 968 // Verify ingest pipeline 969 myURL = fmt.Sprintf("%s%s:%d%s", httpProtocol, f.ExternalIP, esPort, ingestPipelinePath) 970 resp, _, err = sendRequest("GET", myURL, host, false, headers, "") 971 if err != nil { 972 t.Fatalf("Failed to retrieve document %v", err) 973 } 974 if resp.StatusCode != http.StatusOK { 975 t.Fatalf("Expected response code %d from GET but got %d: (%v)", http.StatusOK, resp.StatusCode, resp) 976 } 977 fmt.Println(" ==> IngestNodes pipeline " + docPath + " retrieved") 978 } 979 } 980 981 if verifyVMOIndex { 982 // Verify presence of .vmo index added by backup/restore process 983 myURL = fmt.Sprintf("%s%s:%d/.vmo", httpProtocol, f.ExternalIP, esPort) 984 resp, _, err = sendRequest("GET", myURL, host, false, headers, "") 985 if err != nil { 986 t.Fatalf("Failed to check for VMO index %v", err) 987 } 988 if resp.StatusCode != http.StatusOK { 989 t.Fatalf("Expected response code %d from GET but got %d: (%v)", http.StatusOK, resp.StatusCode, resp) 990 } 991 fmt.Println(" ==> .vmo index observed") 992 } 993 994 // DELETE message document via elasticsearch HTTP API if we are tearing down 995 if !testutil.SkipTeardown(f) { 996 // Delete log record 997 for i := 1; i <= 100; i++ { 998 myURL = fmt.Sprintf("%s%s:%d%s%d", httpProtocol, f.ExternalIP, esPort, docPath, i) 999 resp, _, err = sendRequest("DELETE", myURL, host, false, headers, "") 1000 if err != nil { 1001 t.Fatal(err) 1002 } 1003 if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusAccepted { 1004 t.Fatalf("Expected response code %d or %d from DELETE on %s but got %d: (%v)", http.StatusOK, http.StatusAccepted, myURL, resp.StatusCode, resp) 1005 } 1006 } 1007 fmt.Println(" ==> 100 Documents from " + docPath + " deleted") 1008 if verifyIngestPipeline { 1009 // Delete ingest pipeline 1010 myURL = fmt.Sprintf("%s%s:%d%s", httpProtocol, f.ExternalIP, esPort, ingestPipelinePath) 1011 resp, _, err = sendRequest("DELETE", myURL, host, false, headers, "") 1012 if err != nil { 1013 t.Fatal(err) 1014 } 1015 if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusAccepted { 1016 t.Fatalf("Expected response code %d or %d from DELETE on %s but got %d: (%v)", http.StatusOK, http.StatusAccepted, myURL, resp.StatusCode, resp) 1017 } 1018 fmt.Println(" ==> IngestNodes pipeline" + docPath + " deleted") 1019 } 1020 } 1021 } 1022 } 1023 1024 func verifyKibana(t *testing.T, vmo *vmcontrollerv1.VerrazzanoMonitoringInstance) { 1025 f := framework.Global 1026 var httpProtocol, myURL, host, body string 1027 var resp *http.Response 1028 var err error 1029 var kbPort, esPort int32 1030 var headers = map[string]string{} 1031 1032 if !vmo.Spec.Kibana.Enabled { 1033 return 1034 } 1035 1036 index := strings.ToLower("verifyKibana") 1037 docPath := "/" + index + "/doc" 1038 1039 // What port should we use? 1040 if f.Ingress { 1041 kbPort = getPortFromService(t, f.OperatorNamespace, f.IngressControllerSvcName) 1042 esPort = kbPort 1043 httpProtocol = "https://" 1044 } else { 1045 kbPort = getPortFromService(t, f.Namespace, constants.VMOServiceNamePrefix+vmo.Name+"-"+config.OpenSearchDashboards.Name) 1046 esPort = getPortFromService(t, f.Namespace, constants.VMOServiceNamePrefix+vmo.Name+"-"+config.ElasticsearchIngest.Name) 1047 httpProtocol = "http://" 1048 } 1049 1050 // Verify service endpoint connectivity 1051 waitForEndpoint(t, vmo, "Kibana", kbPort, "/api/status") 1052 fmt.Println(" ==> Service endpoint is available") 1053 1054 testMessage := "verifyKibana." + f.RunID 1055 if testutil.RunBeforePhase(f) { 1056 1057 doc := make(map[string]interface{}) 1058 doc["user"] = f.RunID 1059 doc["post_date"] = time.Now().Format(time.RFC3339) 1060 doc["message"] = testMessage 1061 jsonPayload, marshalErr := json.Marshal(doc) 1062 if marshalErr != nil { 1063 t.Fatal(marshalErr) 1064 } 1065 1066 myURL = fmt.Sprintf("%s%s:%d%s", httpProtocol, f.ExternalIP, esPort, docPath) 1067 host = "elasticsearch." + vmo.Spec.URI 1068 headers["Content-Type"] = "application/json" 1069 resp, _, err = sendRequest("POST", myURL, host, false, headers, string(jsonPayload)) 1070 if err != nil { 1071 t.Fatal(err) 1072 } 1073 if resp.StatusCode != http.StatusCreated { 1074 t.Fatalf("Expected response code %d from POST but got %d: (%v)", http.StatusCreated, resp.StatusCode, resp) 1075 } 1076 fmt.Println(" ==> Document " + docPath + " created") 1077 // Deal with possible Elasticsearch index delay on a newly created document 1078 time.Sleep(10 * time.Second) 1079 } 1080 if testutil.RunAfterPhase(f) { 1081 var query = ` 1082 { 1083 "query":{ 1084 "query_string":{ 1085 "fields":[ 1086 "user" 1087 ], 1088 "query":"` + f.RunID + `" 1089 } 1090 } 1091 } 1092 ` 1093 myURL = fmt.Sprintf("%s%s:%d%s", httpProtocol, f.ExternalIP, kbPort, "/elasticsearch/"+index+"/_search") 1094 host = "kibana." + vmo.Spec.URI 1095 headers["Content-Type"] = "application/json" 1096 headers["kbn-version"] = f.ElasticsearchVersion 1097 resp, body, err = sendRequest("POST", myURL, host, false, headers, query) 1098 if err != nil { 1099 t.Fatal(err) 1100 } 1101 if resp.StatusCode != http.StatusOK { 1102 t.Fatalf("Expected response code %d from POST but got %d: (%v)", http.StatusOK, resp.StatusCode, resp) 1103 } 1104 1105 var jsonResp map[string]interface{} 1106 if unmarshalErr := json.Unmarshal([]byte(body), &jsonResp); unmarshalErr != nil { 1107 t.Fatal(unmarshalErr) 1108 } 1109 1110 if !containsKeyWithValue(jsonResp, "message", testMessage) { 1111 t.Fatalf("response body %s does not contain expected message value %s", body, testMessage) 1112 } 1113 1114 // DELETE index via elasticsearch HTTP API if we are tearing down 1115 if !testutil.SkipTeardown(f) { 1116 1117 myURL = fmt.Sprintf("%s%s:%d%s", httpProtocol, f.ExternalIP, esPort, "/"+index) 1118 host = "elasticsearch." + vmo.Spec.URI 1119 resp, _, err = sendRequest("DELETE", myURL, host, false, headers, "") 1120 if err != nil { 1121 t.Fatal(err) 1122 } 1123 if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusAccepted { 1124 t.Fatalf("Expected response code %d or %d from DELETE on %s but got %d: (%v)", http.StatusOK, http.StatusAccepted, myURL, resp.StatusCode, resp) 1125 } 1126 fmt.Println(" ==> Index " + "/" + index + " deleted") 1127 } 1128 } 1129 } 1130 1131 func verifyVMODeploymentWithIngress(t *testing.T, vmo *vmcontrollerv1.VerrazzanoMonitoringInstance, username, password string) { 1132 f := framework.Global 1133 1134 type ingress struct { 1135 componentName string 1136 endpointName string 1137 deploymentName string 1138 urlPath string 1139 replicas int32 1140 } 1141 ingressMappings := []ingress{ 1142 {"api", "api", constants.VMOServiceNamePrefix + vmo.Name + "-" + config.API.Name, "/prometheus/config", 1}, 1143 {"grafana", "grafana", constants.VMOServiceNamePrefix + vmo.Name + "-" + config.Grafana.Name, "", 1}, 1144 {"elasticsearch-ingest", "elasticsearch", constants.VMOServiceNamePrefix + vmo.Name + "-" + config.ElasticsearchIngest.Name, "", 1}, 1145 {"kibana", "kibana", constants.VMOServiceNamePrefix + vmo.Name + "-" + config.Kibana.Name, "/app/kibana", 1}, 1146 } 1147 1148 // Verify VMO instance deployments 1149 1150 fmt.Println("when junk credentials provided Mapping of VMO Endpoints") 1151 for p := range ingressMappings { 1152 err := testutil.WaitForDeploymentAvailable(f.Namespace, ingressMappings[p].deploymentName, 1153 ingressMappings[p].replicas, testutil.DefaultRetry, f.KubeClient) 1154 if err != nil { 1155 t.Fatal(err) 1156 } 1157 } 1158 1159 fmt.Println("Verifing Ingress Controller service.") 1160 ingressControllerSvc, err := testutil.WaitForService(f.OperatorNamespace, f.IngressControllerSvcName, testutil.DefaultRetry, f.KubeClient) 1161 if err != nil { 1162 t.Fatal(err) 1163 } 1164 fmt.Printf("IngressControllerSvc:%v", ingressControllerSvc) 1165 1166 for i := range ingressMappings { 1167 // Verify service endpoint connectivity through ingress controller 1168 1169 if ingressControllerSvc.Spec.Type == corev1.ServiceTypeLoadBalancer { 1170 if err := testutil.WaitForEndpointAvailableWithAuth( 1171 ingressMappings[i].endpointName, 1172 vmo.Spec.URI, 1173 "", 1174 ingressControllerSvc.Spec.Ports[0].Port, 1175 ingressMappings[i].urlPath, 1176 http.StatusOK, 1177 testutil.DefaultRetry, 1178 username, 1179 password); err != nil { 1180 t.Fatal(err) 1181 } 1182 } else { 1183 if err := testutil.WaitForEndpointAvailableWithAuth( 1184 ingressMappings[i].endpointName, 1185 vmo.Spec.URI, 1186 f.ExternalIP, 1187 ingressControllerSvc.Spec.Ports[0].NodePort, 1188 ingressMappings[i].urlPath, 1189 http.StatusOK, 1190 testutil.DefaultRetry, 1191 username, 1192 password); err != nil { 1193 t.Fatal(err) 1194 } 1195 } 1196 1197 fmt.Println(ingressMappings[i].componentName + " ==> service endpoint found through ingress-controller") 1198 } 1199 1200 for i := range ingressMappings { 1201 // Verify 401 error returned by ingress controller when no credentials supplied 1202 1203 if ingressControllerSvc.Spec.Type == corev1.ServiceTypeLoadBalancer { 1204 if err := testutil.WaitForEndpointAvailableWithAuth( 1205 ingressMappings[i].endpointName, 1206 vmo.Spec.URI, 1207 "", 1208 ingressControllerSvc.Spec.Ports[0].Port, 1209 ingressMappings[i].urlPath, 1210 http.StatusUnauthorized, 1211 testutil.DefaultRetry, 1212 "", 1213 ""); err != nil { 1214 t.Fatal(err) 1215 } 1216 } else { 1217 if err := testutil.WaitForEndpointAvailableWithAuth( 1218 ingressMappings[i].endpointName, 1219 vmo.Spec.URI, 1220 f.ExternalIP, 1221 ingressControllerSvc.Spec.Ports[0].NodePort, 1222 ingressMappings[i].urlPath, 1223 http.StatusUnauthorized, 1224 testutil.DefaultRetry, 1225 "", 1226 ""); err != nil { 1227 t.Fatal(err) 1228 } 1229 } 1230 1231 fmt.Println(ingressMappings[i].componentName + " ==> 401 error successfully returned from ingress-controller when junk credentials provided") 1232 } 1233 } 1234 1235 // containsKeyWithValue recursively searches the specified JSON for the first matched key and returns the value as a string. 1236 // If the key is not found, the empty string is returned. 1237 func containsKeyWithValue(v interface{}, key, value string) bool { 1238 switch vv := v.(type) { 1239 case map[string]interface{}: 1240 for k, v := range vv { 1241 if k == key { 1242 // Base case 1243 return fmt.Sprintf("%v", v) == value 1244 } 1245 if containsKeyWithValue(v, key, value) { 1246 return true 1247 } 1248 } 1249 case []interface{}: 1250 for _, v := range vv { 1251 if containsKeyWithValue(v, key, value) { 1252 return true 1253 } 1254 } 1255 } 1256 return false 1257 } 1258 1259 // waitForKeyWithValueResponse performs a GET and calls containsKeyWithValue each time. 1260 func waitForKeyWithValueResponse(host, externalIP string, port int32, urlPath, key, value string) error { 1261 f := framework.Global 1262 var httpProtocol, body string 1263 var err error 1264 var headers = map[string]string{} 1265 1266 // Always use https with Ingress Controller 1267 if f.Ingress { 1268 httpProtocol = "https://" 1269 } else { 1270 httpProtocol = "http://" 1271 } 1272 endpointURL := fmt.Sprintf("%s%s:%d%s", httpProtocol, externalIP, port, urlPath) 1273 fmt.Printf(" ==> Verifying endpoint (%s) with key:%s and value:%s\n", endpointURL, key, value) 1274 1275 err = testutil.Retry(testutil.DefaultRetry, func() (bool, error) { 1276 _, body, err = sendRequest("GET", endpointURL, host, false, headers, "") 1277 var jsonResp map[string]interface{} 1278 if err := json.Unmarshal([]byte(body), &jsonResp); err == nil { 1279 found := containsKeyWithValue(jsonResp, key, value) 1280 return found, nil 1281 } 1282 return false, err 1283 }) 1284 return err 1285 } 1286 1287 // readTestDashboardConfig returns a dashboard as a JSON-encoded string whose title is the specified id. 1288 func readTestDashboardConfig(id string) (string, error) { 1289 dashboardConfig, readErr := ioutil.ReadFile("files/grafana.dashboard.json") 1290 if readErr != nil { 1291 return "", readErr 1292 } 1293 1294 var dbData map[string]interface{} 1295 if err := json.Unmarshal(dashboardConfig, &dbData); err != nil { 1296 return "", err 1297 } 1298 dbData["title"] = id 1299 dbData["overwrite"] = "true" 1300 1301 // Create and populate outer JSON object 1302 jsonPayload := make(map[string]interface{}) 1303 jsonPayload["dashboard"] = dbData 1304 config, marshalErr := json.MarshalIndent(jsonPayload, "", " ") 1305 if marshalErr != nil { 1306 return "", marshalErr 1307 } 1308 return string(config), nil 1309 } 1310 1311 // Helper function to return the port for a given component 1312 func getPortFromService(t *testing.T, namespace, name string) int32 { 1313 f := framework.Global 1314 1315 // Get the Service 1316 mySvc, err := testutil.WaitForService(namespace, name, testutil.DefaultRetry, f.KubeClient) 1317 if err != nil { 1318 t.Fatal(err) 1319 } 1320 1321 // Return the port 1322 return mySvc.Spec.Ports[0].NodePort 1323 } 1324 1325 // Call appropriate Wait function depending on whether or not we have ingress 1326 func waitForEndpoint(t *testing.T, vmo *vmcontrollerv1.VerrazzanoMonitoringInstance, componentName string, componentPort int32, componentURL string) { 1327 f := framework.Global 1328 1329 if f.Ingress { 1330 if err := testutil.WaitForEndpointAvailableWithAuth( 1331 componentName, 1332 vmo.Spec.URI, 1333 f.ExternalIP, 1334 componentPort, 1335 componentURL, 1336 http.StatusOK, 1337 testutil.DefaultRetry, 1338 username, 1339 password); err != nil { 1340 t.Fatal(err) 1341 } 1342 1343 // If not using ingress... 1344 } else { 1345 if err := testutil.WaitForEndpointAvailable( 1346 componentName, 1347 f.ExternalIP, 1348 componentPort, 1349 componentURL, 1350 http.StatusOK, 1351 testutil.DefaultRetry); err != nil { 1352 t.Fatal(err) 1353 } 1354 } 1355 } 1356 1357 func sendRequest(action, myURL, host string, useAuth bool, headers map[string]string, payload string) (*http.Response, string, error) { 1358 return sendRequestWithUserPassword(action, myURL, host, useAuth, headers, payload, username, password) 1359 } 1360 1361 // Put together an http.Request with or without ingress controller 1362 // Returns an immediate response; no waiting. 1363 func sendRequestWithUserPassword(action, myURL, host string, useAuth bool, headers map[string]string, payload string, reqUserName string, reqPassword string) (*http.Response, string, error) { 1364 f := framework.Global 1365 var err error 1366 1367 tr := &http.Transport{ 1368 TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, //nolint:gosec //#gosec G402 1369 TLSHandshakeTimeout: 10 * time.Second, 1370 // Match Cirith's default timeouts 1371 ResponseHeaderTimeout: 20 * time.Second, 1372 ExpectContinueTimeout: 1 * time.Second, 1373 } 1374 client := &http.Client{Transport: tr, Timeout: 300 * time.Second} 1375 tURL := url.URL{} 1376 1377 // Set proxy for http client - needs to be done unless using localhost 1378 proxyURL := os.Getenv("http_proxy") 1379 if proxyURL != "" { 1380 tURLProxy, _ := tURL.Parse(proxyURL) 1381 tr.Proxy = http.ProxyURL(tURLProxy) 1382 } 1383 1384 // fmt.Printf(" --> Request: %s - %s\n", action, myURL) 1385 req, _ := http.NewRequest(action, myURL, strings.NewReader(payload)) 1386 req.Header.Add("Accept", "*/*") 1387 1388 // Add any headers to the request 1389 for k := range headers { 1390 req.Header.Add(k, headers[k]) 1391 } 1392 1393 // If using ingress, we need to set the host... 1394 if f.Ingress { 1395 req.Host = host 1396 } 1397 1398 // Use Basic Auth if using ingress - or if requested... 1399 if f.Ingress || useAuth { 1400 req.SetBasicAuth(reqUserName, reqPassword) 1401 } 1402 1403 // Send the request 1404 resp, err := client.Do(req) 1405 if err != nil { 1406 return nil, "", err 1407 } 1408 defer resp.Body.Close() 1409 1410 // Extract the body 1411 body, _ := ioutil.ReadAll(resp.Body) 1412 // fmt.Printf(" --> Body: %s", string(body)) 1413 1414 return resp, string(body), err 1415 } 1416 1417 // generateRandomString returns a base64 encoded generated random string. 1418 func generateRandomString() string { 1419 b := make([]byte, 32) 1420 rand.Read(b) 1421 return base64.StdEncoding.EncodeToString(b) 1422 }