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  }