github.com/verrazzano/verrazzano@v1.7.1/tests/e2e/examples/helidon/helidon_example_test.go (about)

     1  // Copyright (c) 2020, 2023, Oracle and/or its affiliates.
     2  // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
     3  
     4  package helidon
     5  
     6  import (
     7  	"fmt"
     8  	"io"
     9  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    10  	"net/http"
    11  	"os"
    12  	"strings"
    13  	"time"
    14  
    15  	"github.com/hashicorp/go-retryablehttp"
    16  	. "github.com/onsi/ginkgo/v2"
    17  	. "github.com/onsi/gomega"
    18  	"github.com/verrazzano/verrazzano/pkg/k8sutil"
    19  	"github.com/verrazzano/verrazzano/tests/e2e/pkg"
    20  	dump "github.com/verrazzano/verrazzano/tests/e2e/pkg/test/clusterdump"
    21  	"github.com/verrazzano/verrazzano/tests/e2e/pkg/test/framework"
    22  	"github.com/verrazzano/verrazzano/tests/e2e/pkg/test/framework/metrics"
    23  )
    24  
    25  const (
    26  	longWaitTimeout            = 20 * time.Minute
    27  	longPollingInterval        = 20 * time.Second
    28  	shortPollingInterval       = 10 * time.Second
    29  	shortWaitTimeout           = 5 * time.Minute
    30  	imagePullWaitTimeout       = 40 * time.Minute
    31  	imagePullPollingInterval   = 30 * time.Second
    32  	skipVerifications          = "Skip Verifications"
    33  	helloHelidon               = "hello-helidon"
    34  	nodeExporterJobName        = "node-exporter"
    35  	helloHelidonDeploymentName = "hello-helidon-deployment"
    36  
    37  	ingress        = "hello-helidon-ingress-rule"
    38  	helidonService = "hello-helidon-deployment"
    39  )
    40  
    41  var (
    42  	t                  = framework.NewTestFramework("helidon")
    43  	generatedNamespace = pkg.GenerateNamespace(helloHelidon)
    44  	// yamlApplier              = k8sutil.YAMLApplier{}
    45  	expectedPodsHelloHelidon = []string{"hello-helidon-deployment"}
    46  	host                     = ""
    47  	isMinVersion140          bool
    48  	metricsTest              pkg.MetricsTest
    49  )
    50  
    51  var beforeSuite = t.BeforeSuiteFunc(func() {
    52  
    53  	if !skipDeploy {
    54  		start := time.Now()
    55  		pkg.DeployHelloHelidonApplication(namespace, "", istioInjection, helloHelidonComponent, helloHelidonAppConfig)
    56  		metrics.Emit(t.Metrics.With("deployment_elapsed_time", time.Since(start).Milliseconds()))
    57  
    58  		t.Logs.Info("Container image pull check")
    59  		Eventually(func() bool {
    60  			return pkg.ContainerImagePullWait(namespace, expectedPodsHelloHelidon)
    61  		}, imagePullWaitTimeout, imagePullPollingInterval).Should(BeTrue())
    62  	}
    63  	kubeconfigPath, err := k8sutil.GetKubeConfigLocation()
    64  	if err != nil {
    65  		Fail(fmt.Sprintf("Failed to get default kubeconfig path: %s", err.Error()))
    66  	}
    67  	isMinVersion140, err = pkg.IsVerrazzanoMinVersion("1.4.0", kubeconfigPath)
    68  	if err != nil {
    69  		Fail(err.Error())
    70  	}
    71  
    72  	if !skipVerify {
    73  		// Verify hello-helidon-deployment pod is running
    74  		// GIVEN OAM hello-helidon app is deployed
    75  		// WHEN the component and appconfig are created
    76  		// THEN the expected pod must be running in the test namespace
    77  		t.Logs.Info("Helidon Example: check expected pods are running")
    78  		Eventually(func() bool {
    79  			result, err := pkg.PodsRunning(namespace, expectedPodsHelloHelidon)
    80  			if err != nil {
    81  				AbortSuite(fmt.Sprintf("One or more pods are not running in the namespace: %v, error: %v", namespace, err))
    82  			}
    83  			return result
    84  		}, longWaitTimeout, longPollingInterval).Should(BeTrue(), "Helidon Example Failed to Deploy: Pods are not ready")
    85  
    86  		t.Logs.Info("Helidon Example: check expected Services are running")
    87  		Eventually(func() bool {
    88  			result, err := pkg.DoesServiceExist(namespace, helidonService)
    89  			if err != nil {
    90  				AbortSuite(fmt.Sprintf("Helidon Service %s is not running in the namespace: %v, error: %v", helidonService, namespace, err))
    91  			}
    92  			return result
    93  		}, longWaitTimeout, longPollingInterval).Should(BeTrue(), "Helidon Example Failed to Deploy: Services are not ready")
    94  
    95  		t.Logs.Info("Helidon Example: check expected VirtualService is ready")
    96  		Eventually(func() bool {
    97  			result, err := pkg.DoesVirtualServiceExist(namespace, ingress)
    98  			if err != nil {
    99  				AbortSuite(fmt.Sprintf("Helidon VirtualService %s is not running in the namespace: %v, error: %v", ingress, namespace, err))
   100  			}
   101  			return result
   102  		}, shortWaitTimeout, longPollingInterval).Should(BeTrue(), "Helidon Example Failed to Deploy: VirtualService is not ready")
   103  
   104  		var err error
   105  		// Get the host from the Istio gateway resource.
   106  		start := time.Now()
   107  		t.Logs.Info("Helidon Example: check expected Gateway is ready")
   108  		Eventually(func() (string, error) {
   109  			host, err = k8sutil.GetHostnameFromGateway(namespace, "")
   110  			return host, err
   111  		}, shortWaitTimeout, shortPollingInterval).Should(Not(BeEmpty()), "Helidon Example: Gateway is not ready")
   112  		metrics.Emit(t.Metrics.With("get_host_name_elapsed_time", time.Since(start).Milliseconds()))
   113  
   114  		// validate if manualscalertrait applied, then pod count should be 2
   115  		// examples/hello-helidon/hello-helidon-app-scaler-trait.yaml
   116  		if strings.Contains(helloHelidonAppConfig, "hello-helidon-app-scaler-trait.yaml") {
   117  			t.Logs.Info("Helidon Example: check expected pods are running")
   118  
   119  			Eventually(func() bool {
   120  				return isDeploymentSetUpdated()
   121  			}, longWaitTimeout, longPollingInterval).Should(BeTrue())
   122  		}
   123  	}
   124  
   125  	kubeconfig, err := k8sutil.GetKubeConfigLocation()
   126  	if err != nil {
   127  		AbortSuite(fmt.Sprintf("Failed to get the Kubeconfig location for the cluster: %v", err))
   128  	}
   129  	metricsTest, err = pkg.NewMetricsTest(kubeconfig, map[string]string{})
   130  	if err != nil {
   131  		AbortSuite(fmt.Sprintf("Failed to create the Metrics test object: %v", err))
   132  	}
   133  
   134  	beforeSuitePassed = true
   135  })
   136  
   137  var _ = BeforeSuite(beforeSuite)
   138  
   139  var failed = false
   140  var beforeSuitePassed = false
   141  
   142  var _ = t.AfterEach(func() {
   143  	failed = failed || CurrentSpecReport().Failed()
   144  })
   145  
   146  var afterSuite = t.AfterSuiteFunc(func() {
   147  	if failed || !beforeSuitePassed {
   148  		dump.ExecuteBugReport(namespace)
   149  	}
   150  	if !skipUndeploy {
   151  		start := time.Now()
   152  		pkg.UndeployHelloHelidonApplication(namespace, helloHelidonComponent, helloHelidonAppConfig)
   153  		metrics.Emit(t.Metrics.With("undeployment_elapsed_time", time.Since(start).Milliseconds()))
   154  	}
   155  })
   156  
   157  var _ = AfterSuite(afterSuite)
   158  
   159  var _ = t.Describe("Hello Helidon OAM App test", Label("f:app-lcm.oam",
   160  	"f:app-lcm.helidon-workload"), func() {
   161  	var host = ""
   162  	var err error
   163  	// Get the host from the Istio gateway resource.
   164  	// GIVEN the Istio gateway for the hello-helidon namespace
   165  	// WHEN GetHostnameFromGateway is called
   166  	// THEN return the host name found in the gateway.
   167  	t.BeforeEach(func() {
   168  		Eventually(func() (string, error) {
   169  			host, err = k8sutil.GetHostnameFromGateway(namespace, "")
   170  			return host, err
   171  		}, shortWaitTimeout, shortPollingInterval).Should(Not(BeEmpty()))
   172  	})
   173  
   174  	// Verify Hello Helidon app is working
   175  	// GIVEN OAM hello-helidon app is deployed
   176  	// WHEN the component and appconfig with ingress trait are created
   177  	// THEN the application endpoint must be accessible
   178  	t.Describe("for Ingress.", Label("f:mesh.ingress"), func() {
   179  		t.It("Access /greet App Url.", func() {
   180  			if skipVerify {
   181  				Skip(skipVerifications)
   182  			}
   183  			url := fmt.Sprintf("https://%s/greet", host)
   184  			Eventually(func() bool {
   185  				return appEndpointAccessible(url, host)
   186  			}, longWaitTimeout, longPollingInterval).Should(BeTrue())
   187  		})
   188  	})
   189  
   190  	t.Describe("supports Selector", Label("f:selector.labels"), func() {
   191  		t.It("Matchlabels and Matchexpressions", func() {
   192  			if skipVerify {
   193  				Skip(skipVerifications)
   194  			}
   195  			kubeConfig, err := k8sutil.GetKubeConfigLocation()
   196  			if err != nil {
   197  				Skip(skipVerifications)
   198  			}
   199  			if ok, _ := pkg.IsVerrazzanoMinVersion("1.4.0", kubeConfig); !ok {
   200  				Skip(skipVerifications)
   201  			}
   202  			Eventually(func() bool {
   203  				return isDeploymentLabelSelectorValuesMatched()
   204  			}, longWaitTimeout, longPollingInterval).Should(BeTrue())
   205  		})
   206  	})
   207  	// Verify Prometheus scraped targets
   208  	// GIVEN OAM hello-helidon app is deployed
   209  	// WHEN the component and appconfig without metrics-trait(using default) are created
   210  	// THEN the application's all scrape targets must be healthy
   211  	t.Describe("for Metrics.", Label("f:observability.monitoring.prom"), FlakeAttempts(5), func() {
   212  		t.It("Verify all scrape targets are healthy for the application", func() {
   213  			if skipVerify {
   214  				Skip(skipVerifications)
   215  			}
   216  			Eventually(func() (bool, error) {
   217  				var componentNames = []string{"hello-helidon-component"}
   218  				return pkg.ScrapeTargetsHealthy(pkg.GetScrapePools(namespace, helloHelidon, componentNames, isMinVersion140))
   219  			}, shortWaitTimeout, shortPollingInterval).Should(BeTrue())
   220  		})
   221  	})
   222  
   223  	t.Context("Logging.", Label("f:observability.logging.es"), FlakeAttempts(5), func() {
   224  		var indexName string
   225  		Eventually(func() error {
   226  			indexName, err = pkg.GetOpenSearchAppIndex(namespace)
   227  			return err
   228  		}, shortWaitTimeout, shortPollingInterval).Should(BeNil(), "Expected to get OpenSearch App Index")
   229  
   230  		// GIVEN an application with logging enabled
   231  		// WHEN the Opensearch index is retrieved
   232  		// THEN verify that it is found
   233  		t.It("Verify Opensearch index exists", func() {
   234  			if skipVerify {
   235  				Skip(skipVerifications)
   236  			}
   237  			Eventually(func() bool {
   238  				return pkg.LogIndexFound(indexName)
   239  			}, longWaitTimeout, longPollingInterval).Should(BeTrue(), "Expected to find log index for hello helidon")
   240  		})
   241  
   242  		// GIVEN an application with logging enabled
   243  		// WHEN the log records are retrieved from the Opensearch index
   244  		// THEN verify that at least one recent log record is found
   245  		t.It("Verify recent Opensearch log record exists", func() {
   246  			if skipVerify {
   247  				Skip(skipVerifications)
   248  			}
   249  			if os.Getenv("TEST_ENV") != "LRE" {
   250  				Eventually(func() bool {
   251  					return pkg.FindLog(indexName,
   252  						[]pkg.Match{
   253  							{Key: "kubernetes.labels.app_oam_dev\\/component", Value: "hello-helidon-component"},
   254  							{Key: "kubernetes.labels.app_oam_dev\\/name", Value: helloHelidon},
   255  							{Key: "kubernetes.container_name", Value: "hello-helidon-container"}},
   256  						[]pkg.Match{})
   257  				}, longWaitTimeout, longPollingInterval).Should(BeTrue(), "Expected to find a recent log record")
   258  			}
   259  		})
   260  	})
   261  
   262  })
   263  
   264  // isDeploymentLabelSelectorValuesMatched tests labelselector must exists into deployment
   265  // also must have values into matchlabels & matchexpressions
   266  
   267  func isDeploymentLabelSelectorValuesMatched() bool {
   268  	// fetch labelselector from hello helidon deployment
   269  	labelSelector, err := pkg.GetDeploymentLabelSelector(namespace, helloHelidonDeploymentName)
   270  	if err != nil {
   271  		return false
   272  	}
   273  
   274  	/*
   275  		// Putting the exact value match on hold, reconciling during vz upgrade is wip
   276  		// check labelselector matchlabels must have at least 1 pair of matchlabels arg
   277  		if val, ok := labelSelector.MatchLabels["app"]; !ok || val != helloHelidon {
   278  			return false
   279  		}
   280  		// check labelselector matchexpressions must not be empty
   281  		if len(labelSelector.MatchExpressions) == 0 {
   282  			return false
   283  		}
   284  	*/
   285  	return labelSelector != nil
   286  }
   287  
   288  func helloHelidonPodsRunning() bool {
   289  	result, err := pkg.PodsRunning(namespace, expectedPodsHelloHelidon)
   290  	if err != nil {
   291  		AbortSuite(fmt.Sprintf("One or more pods are not running in the namespace: %v, error: %v", namespace, err))
   292  	}
   293  	return result
   294  }
   295  
   296  func appEndpointAccessible(url string, hostname string) bool {
   297  	req, err := retryablehttp.NewRequest("GET", url, nil)
   298  	if err != nil {
   299  		t.Logs.Errorf("Unexpected error while creating new request=%v", err)
   300  		return false
   301  	}
   302  
   303  	kubeconfigPath, err := k8sutil.GetKubeConfigLocation()
   304  	if err != nil {
   305  		t.Logs.Errorf("Unexpected error while getting kubeconfig location=%v", err)
   306  		return false
   307  	}
   308  
   309  	httpClient, err := pkg.GetVerrazzanoHTTPClient(kubeconfigPath)
   310  	if err != nil {
   311  		t.Logs.Errorf("Unexpected error while getting new httpClient=%v", err)
   312  		return false
   313  	}
   314  	req.Host = hostname
   315  	req.Close = true
   316  	resp, err := httpClient.Do(req)
   317  	if err != nil {
   318  		t.Logs.Errorf("Unexpected error while making http request=%v", err)
   319  		if resp != nil && resp.Body != nil {
   320  			bodyRaw, err := io.ReadAll(resp.Body)
   321  			if err != nil {
   322  				t.Logs.Errorf("Unexpected error while marshallling error response=%v", err)
   323  				return false
   324  			}
   325  
   326  			t.Logs.Errorf("Error Response=%v", string(bodyRaw))
   327  			resp.Body.Close()
   328  		}
   329  		return false
   330  	}
   331  
   332  	bodyRaw, err := io.ReadAll(resp.Body)
   333  	resp.Body.Close()
   334  	if err != nil {
   335  		t.Logs.Errorf("Unexpected error marshallling response=%v", err)
   336  		return false
   337  	}
   338  	if resp.StatusCode != http.StatusOK {
   339  		t.Logs.Errorf("Unexpected status code=%v", resp.StatusCode)
   340  		return false
   341  	}
   342  	// HTTP Server headers should never be returned.
   343  	for headerName, headerValues := range resp.Header {
   344  		if strings.EqualFold(headerName, "Server") {
   345  			t.Logs.Errorf("Unexpected Server header=%v", headerValues)
   346  			return false
   347  		}
   348  	}
   349  	bodyStr := string(bodyRaw)
   350  	if !strings.Contains(bodyStr, "Hello World") {
   351  		t.Logs.Errorf("Unexpected response body=%v", bodyStr)
   352  		return false
   353  	}
   354  	return true
   355  }
   356  
   357  func appMetricsExists() bool {
   358  	return metricsTest.MetricsExist("base_jvm_uptime_seconds", map[string]string{"app": helloHelidon})
   359  }
   360  
   361  func appComponentMetricsExists() bool {
   362  	return metricsTest.MetricsExist("vendor_requests_count_total", map[string]string{"app_oam_dev_name": helloHelidon})
   363  }
   364  
   365  func appConfigMetricsExists() bool {
   366  	return metricsTest.MetricsExist("vendor_requests_count_total", map[string]string{"app_oam_dev_component": "hello-helidon-component"})
   367  }
   368  
   369  func nodeExporterProcsRunning() bool {
   370  	return metricsTest.MetricsExist("node_procs_running", map[string]string{"job": nodeExporterJobName})
   371  }
   372  
   373  func nodeExporterDiskIoNow() bool {
   374  	return metricsTest.MetricsExist("node_disk_io_now", map[string]string{"job": nodeExporterJobName})
   375  }
   376  
   377  // isDeploymentSetUpdated returns
   378  // replicas from examples/hello-helidon/hello-helidon-app-scaler-trait.yaml
   379  // currently configured 2
   380  func isDeploymentSetUpdated() bool {
   381  	pods, err := pkg.ListPods(namespace, metav1.ListOptions{})
   382  	if err != nil {
   383  		t.Logs.Errorf("Unexpected error while listing the deployments error response=%v", err)
   384  		return false
   385  	}
   386  	t.Logs.Info("current pod count...", len(pods.Items))
   387  	if len(pods.Items) < 2 {
   388  		return false
   389  	} else {
   390  		return true
   391  	}
   392  }