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

     1  // Copyright (c) 2022, 2023, Oracle and/or its affiliates.
     2  // Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
     3  
     4  package helidonsvc
     5  
     6  import (
     7  	"fmt"
     8  	"github.com/hashicorp/go-retryablehttp"
     9  	dump "github.com/verrazzano/verrazzano/tests/e2e/pkg/test/clusterdump"
    10  	"io"
    11  	"net/http"
    12  	"strings"
    13  	"time"
    14  
    15  	. "github.com/onsi/ginkgo/v2"
    16  	. "github.com/onsi/gomega"
    17  	"github.com/verrazzano/verrazzano/pkg/k8s/resource"
    18  	"github.com/verrazzano/verrazzano/pkg/k8sutil"
    19  	"github.com/verrazzano/verrazzano/tests/e2e/pkg"
    20  	"github.com/verrazzano/verrazzano/tests/e2e/pkg/test/framework"
    21  	"github.com/verrazzano/verrazzano/tests/e2e/pkg/test/framework/metrics"
    22  	v1 "k8s.io/api/core/v1"
    23  	"k8s.io/apimachinery/pkg/api/errors"
    24  )
    25  
    26  const (
    27  	longWaitTimeout          = 20 * time.Minute
    28  	longPollingInterval      = 20 * time.Second
    29  	shortPollingInterval     = 10 * time.Second
    30  	shortWaitTimeout         = 5 * time.Minute
    31  	imagePullWaitTimeout     = 40 * time.Minute
    32  	imagePullPollingInterval = 30 * time.Second
    33  	skipVerifications        = "Skip Verifications"
    34  	nodeExporterJobName      = "node-exporter"
    35  )
    36  
    37  const (
    38  	helidonComponentYaml = "testdata/jwt/helidon-svc/hello-helidon-svc-comps.yaml"
    39  	helidonAppYaml       = "testdata/jwt/helidon-svc/hello-helidon-svc-app.yaml"
    40  )
    41  
    42  var (
    43  	t                        = framework.NewTestFramework("helidon")
    44  	generatedNamespace       = pkg.GenerateNamespace("hello-helidon-svc")
    45  	expectedPodsHelloHelidon = []string{"hello-helidon-svc-deployment"}
    46  	metricsTest              pkg.MetricsTest
    47  )
    48  var isMinVersion140 bool
    49  
    50  var beforeSuite = t.BeforeSuiteFunc(func() {
    51  	if !skipDeploy {
    52  		start := time.Now()
    53  		deployHelloHelidonApplication(namespace, "", istioInjection)
    54  		metrics.Emit(t.Metrics.With("deployment_elapsed_time", time.Since(start).Milliseconds()))
    55  	}
    56  
    57  	Eventually(func() bool {
    58  		return pkg.ContainerImagePullWait(namespace, expectedPodsHelloHelidon)
    59  	}, imagePullWaitTimeout, imagePullPollingInterval).Should(BeTrue())
    60  	// Verify hello-helidon-deployment pod is running
    61  	// GIVEN OAM hello-helidon app is deployed
    62  	// WHEN the component and appconfig are created
    63  	// THEN the expected pod must be running in the test namespace
    64  	if !skipVerify {
    65  		Eventually(helloHelidonPodsRunning, longWaitTimeout, longPollingInterval).Should(BeTrue())
    66  	}
    67  	kubeconfigPath, err := k8sutil.GetKubeConfigLocation()
    68  	if err != nil {
    69  		Fail(fmt.Sprintf("Failed to get default kubeconfig path: %s", err.Error()))
    70  	}
    71  	isMinVersion140, err = pkg.IsVerrazzanoMinVersion("1.4.0", kubeconfigPath)
    72  	if err != nil {
    73  		Fail(err.Error())
    74  	}
    75  
    76  	kubeconfig, err := k8sutil.GetKubeConfigLocation()
    77  	if err != nil {
    78  		AbortSuite(fmt.Sprintf("Failed to get the Kubeconfig location for the cluster: %v", err))
    79  	}
    80  	metricsTest, err = pkg.NewMetricsTest(kubeconfig, map[string]string{})
    81  	if err != nil {
    82  		AbortSuite(fmt.Sprintf("Failed to create the Metrics test object: %v", err))
    83  	}
    84  
    85  	beforeSuitePassed = true
    86  })
    87  
    88  var _ = BeforeSuite(beforeSuite)
    89  
    90  var failed = false
    91  var beforeSuitePassed = false
    92  
    93  var _ = t.AfterEach(func() {
    94  	failed = failed || CurrentSpecReport().Failed()
    95  })
    96  
    97  var afterSuite = t.AfterSuiteFunc(func() {
    98  	if failed || !beforeSuitePassed {
    99  		dump.ExecuteBugReport(namespace)
   100  	}
   101  	if !skipUndeploy {
   102  		start := time.Now()
   103  		undeployHelloHelidonApplication(namespace)
   104  		metrics.Emit(t.Metrics.With("undeployment_elapsed_time", time.Since(start).Milliseconds()))
   105  	}
   106  })
   107  
   108  var _ = AfterSuite(afterSuite)
   109  
   110  var _ = t.Describe("Hello Helidon OAM App test", Label("f:app-lcm.oam",
   111  	"f:app-lcm.helidon-workload"), func() {
   112  	var host = ""
   113  	var err error
   114  	// Get the host from the Istio gateway resource.
   115  	// GIVEN the Istio gateway for the hello-helidon namespace
   116  	// WHEN GetHostnameFromGateway is called
   117  	// THEN return the host name found in the gateway.
   118  	t.BeforeEach(func() {
   119  		Eventually(func() (string, error) {
   120  			host, err = k8sutil.GetHostnameFromGateway(namespace, "")
   121  			return host, err
   122  		}, shortWaitTimeout, shortPollingInterval).Should(Not(BeEmpty()))
   123  	})
   124  
   125  	// Verify Hello Helidon app is working
   126  	// GIVEN OAM hello-helidon app is deployed
   127  	// WHEN the component and appconfig with ingress trait are created
   128  	// THEN the application endpoint must be accessible
   129  	t.Describe("for Ingress.", Label("f:mesh.ingress"), func() {
   130  		t.It("Access /greet App Url w/o token and get RBAC denial", func() {
   131  			if skipVerify {
   132  				Skip(skipVerifications)
   133  			}
   134  			url := fmt.Sprintf("https://%s/greet", host)
   135  			Eventually(func() bool {
   136  				return appEndpointAccess(url, host, "", false)
   137  			}, longWaitTimeout, longPollingInterval).Should(BeTrue())
   138  		})
   139  
   140  		t.It("Access /greet App Url with valid token", func() {
   141  			if skipVerify {
   142  				Skip(skipVerifications)
   143  			}
   144  			kc, err := pkg.NewKeycloakAdminRESTClient()
   145  			Expect(err).To(BeNil())
   146  			password := pkg.GetRequiredEnvVarOrFail("REALM_USER_PASSWORD")
   147  			realmName := pkg.GetRequiredEnvVarOrFail("REALM_NAME")
   148  			// check for realm
   149  			_, err = kc.GetRealm(realmName)
   150  			Expect(err).To(BeNil())
   151  			var token string
   152  			token, err = kc.GetToken(realmName, "testuser", password, "appsclient", t.Logs)
   153  			Expect(err).To(BeNil())
   154  			t.Logs.Debugf("Obtained token: %v", token)
   155  			url := fmt.Sprintf("https://%s/greet", host)
   156  			Eventually(func() bool {
   157  				return appEndpointAccess(url, host, token, true)
   158  			}, longWaitTimeout, longPollingInterval).Should(BeTrue())
   159  		})
   160  	})
   161  
   162  	// Verify Prometheus scraped targets
   163  	// GIVEN OAM hello-helidon app is deployed
   164  	// WHEN the component and appconfig without metrics-trait(using default) are created
   165  	// THEN the application scrape targets must be healthy
   166  	t.Describe("for Metrics.", Label("f:observability.monitoring.prom"), FlakeAttempts(5), func() {
   167  		t.It("Verify all scrape targets are healthy for the application", func() {
   168  			if skipVerify {
   169  				Skip(skipVerifications)
   170  			}
   171  			Eventually(func() (bool, error) {
   172  				var componentNames = []string{"hello-helidon-deploy-component"}
   173  				return pkg.ScrapeTargetsHealthy(pkg.GetScrapePools(namespace, "hello-helidon-svc-application", componentNames, isMinVersion140))
   174  			}, shortWaitTimeout, shortPollingInterval).Should(BeTrue())
   175  		})
   176  	})
   177  
   178  	t.Context("Logging.", Label("f:observability.logging.es"), FlakeAttempts(5), func() {
   179  		var indexName string
   180  		Eventually(func() error {
   181  			indexName, err = pkg.GetOpenSearchAppIndex(namespace)
   182  			return err
   183  		}, shortWaitTimeout, shortPollingInterval).Should(BeNil(), "Expected to get OpenSearch App Index")
   184  
   185  		// GIVEN an application with logging enabled
   186  		// WHEN the Opensearch index is retrieved
   187  		// THEN verify that it is found
   188  		t.It("Verify Opensearch index exists", func() {
   189  			if skipVerify {
   190  				Skip(skipVerifications)
   191  			}
   192  			Eventually(func() bool {
   193  				return pkg.LogIndexFound(indexName)
   194  			}, longWaitTimeout, longPollingInterval).Should(BeTrue(), "Expected to find log index for hello helidon")
   195  		})
   196  
   197  		// GIVEN an application with logging enabled
   198  		// WHEN the log records are retrieved from the Opensearch index
   199  		// THEN verify that at least one recent log record is found
   200  		t.It("Verify recent Opensearch log record exists", func() {
   201  			if skipVerify {
   202  				Skip(skipVerifications)
   203  			}
   204  			Eventually(func() bool {
   205  				return pkg.LogRecordFound(indexName, time.Now().Add(-24*time.Hour), map[string]string{
   206  					"kubernetes.labels.app_oam_dev\\/name": "hello-helidon-svc-application",
   207  					"kubernetes.container_name":            "hello-helidon-container",
   208  				})
   209  			}, longWaitTimeout, longPollingInterval).Should(BeTrue(), "Expected to find a recent log record")
   210  			Eventually(func() bool {
   211  				return pkg.LogRecordFound(indexName, time.Now().Add(-24*time.Hour), map[string]string{
   212  					"kubernetes.labels.app_oam_dev\\/component": "hello-helidon-deploy-component",
   213  					"kubernetes.labels.app_oam_dev\\/name":      "hello-helidon-svc-application",
   214  					"kubernetes.container_name":                 "hello-helidon-container",
   215  				})
   216  			}, longWaitTimeout, longPollingInterval).Should(BeTrue(), "Expected to find a recent log record")
   217  		})
   218  	})
   219  
   220  })
   221  
   222  // DeployHelloHelidonApplication deploys the Hello Helidon example application. It accepts an optional
   223  // OCI Log ID that is added as an annotation on the namespace to test the OCI Logging service integration.
   224  func deployHelloHelidonApplication(namespace string, ociLogID string, istioInjection string) {
   225  	pkg.Log(pkg.Info, "Deploy Hello Helidon Application")
   226  	pkg.Log(pkg.Info, fmt.Sprintf("Create namespace %s", namespace))
   227  	Eventually(func() (*v1.Namespace, error) {
   228  		nsLabels := map[string]string{
   229  			"verrazzano-managed": "true",
   230  			"istio-injection":    istioInjection}
   231  
   232  		var annotations map[string]string
   233  		if len(ociLogID) > 0 {
   234  			annotations = make(map[string]string)
   235  			annotations["verrazzano.io/oci-log-id"] = ociLogID
   236  		}
   237  
   238  		return pkg.CreateNamespaceWithAnnotations(namespace, nsLabels, annotations)
   239  	}, shortWaitTimeout, shortPollingInterval).ShouldNot(BeNil(), fmt.Sprintf("Failed to create namespace %s", namespace))
   240  
   241  	pkg.Log(pkg.Info, "Create Hello Helidon component resource")
   242  	Eventually(func() error {
   243  		file, err := pkg.FindTestDataFile(helidonComponentYaml)
   244  		if err != nil {
   245  			return err
   246  		}
   247  		return resource.CreateOrUpdateResourceFromFileInGeneratedNamespace(file, namespace)
   248  	}, shortWaitTimeout, shortPollingInterval).ShouldNot(HaveOccurred(), "Failed to create hello-helidon component resource")
   249  
   250  	pkg.Log(pkg.Info, "Create Hello Helidon application resource")
   251  	Eventually(func() error {
   252  		file, err := pkg.FindTestDataFile(helidonAppYaml)
   253  		if err != nil {
   254  			return err
   255  		}
   256  		return resource.CreateOrUpdateResourceFromFileInGeneratedNamespace(file, namespace)
   257  	}, shortWaitTimeout, shortPollingInterval).ShouldNot(HaveOccurred(), "Failed to create hello-helidon application resource")
   258  }
   259  
   260  // undeployHelloHelidonApplication undeploys the Hello Helidon example application.
   261  func undeployHelloHelidonApplication(namespace string) {
   262  	pkg.Log(pkg.Info, "Undeploy Hello Helidon Application")
   263  	if exists, _ := pkg.DoesNamespaceExist(namespace); exists {
   264  		pkg.Log(pkg.Info, "Delete Hello Helidon application")
   265  		Eventually(func() error {
   266  			file, err := pkg.FindTestDataFile(helidonAppYaml)
   267  			if err != nil {
   268  				return err
   269  			}
   270  			return resource.DeleteResourceFromFileInGeneratedNamespace(file, namespace)
   271  		}, shortWaitTimeout, shortPollingInterval).ShouldNot(HaveOccurred(), "Failed to create hello-helidon application resource")
   272  
   273  		pkg.Log(pkg.Info, "Delete Hello Helidon components")
   274  		Eventually(func() error {
   275  			file, err := pkg.FindTestDataFile(helidonComponentYaml)
   276  			if err != nil {
   277  				return err
   278  			}
   279  			return resource.DeleteResourceFromFileInGeneratedNamespace(file, namespace)
   280  		}, shortWaitTimeout, shortPollingInterval).ShouldNot(HaveOccurred(), "Failed to create hello-helidon component resource")
   281  
   282  		pkg.Log(pkg.Info, "Wait for application pods to terminate")
   283  		Eventually(func() bool {
   284  			podsTerminated, _ := pkg.PodsNotRunning(namespace, expectedPodsHelloHelidon)
   285  			return podsTerminated
   286  		}, shortWaitTimeout, shortPollingInterval).Should(BeTrue())
   287  
   288  		pkg.Log(pkg.Info, fmt.Sprintf("Delete namespace %s", namespace))
   289  		Eventually(func() error {
   290  			return pkg.DeleteNamespace(namespace)
   291  		}, shortWaitTimeout, shortPollingInterval).ShouldNot(HaveOccurred(), fmt.Sprintf("Failed to deleted namespace %s", namespace))
   292  
   293  		pkg.Log(pkg.Info, "Wait for namespace finalizer to be removed")
   294  		Eventually(func() bool {
   295  			return pkg.CheckNamespaceFinalizerRemoved(namespace)
   296  		}, shortWaitTimeout, shortPollingInterval).Should(BeTrue())
   297  
   298  		pkg.Log(pkg.Info, "Wait for namespace to be deleted")
   299  		Eventually(func() bool {
   300  			_, err := pkg.GetNamespace(namespace)
   301  			return err != nil && errors.IsNotFound(err)
   302  		}, shortWaitTimeout, shortPollingInterval).Should(BeTrue())
   303  	}
   304  }
   305  
   306  func helloHelidonPodsRunning() bool {
   307  	result, err := pkg.PodsRunning(namespace, expectedPodsHelloHelidon)
   308  	if err != nil {
   309  		AbortSuite(fmt.Sprintf("One or more pods are not running in the namespace: %v, error: %v", namespace, err))
   310  	}
   311  	return result
   312  }
   313  
   314  func appEndpointAccess(url string, hostname string, token string, requestShouldSucceed bool) bool {
   315  	req, err := retryablehttp.NewRequest("GET", url, nil)
   316  	if err != nil {
   317  		t.Logs.Errorf("Unexpected error=%v", err)
   318  		return false
   319  	}
   320  
   321  	kubeconfigPath, err := k8sutil.GetKubeConfigLocation()
   322  	if err != nil {
   323  		t.Logs.Errorf("Unexpected error=%v", err)
   324  		return false
   325  	}
   326  
   327  	httpClient, err := pkg.GetVerrazzanoHTTPClient(kubeconfigPath)
   328  	if err != nil {
   329  		t.Logs.Errorf("Unexpected error=%v", err)
   330  		return false
   331  	}
   332  
   333  	if len(token) > 0 {
   334  		req.Header.Add("Authorization", fmt.Sprintf("Bearer %v", token))
   335  	}
   336  
   337  	req.Host = hostname
   338  	resp, err := httpClient.Do(req)
   339  	if err != nil {
   340  		t.Logs.Errorf("Unexpected error=%v", err)
   341  		return false
   342  	}
   343  	bodyRaw, err := io.ReadAll(resp.Body)
   344  	resp.Body.Close()
   345  	if err != nil {
   346  		t.Logs.Errorf("Unexpected error=%v", err)
   347  		return false
   348  	}
   349  	if requestShouldSucceed {
   350  		if resp.StatusCode != http.StatusOK {
   351  			t.Logs.Errorf("Unexpected status code=%v", resp.StatusCode)
   352  			return false
   353  		}
   354  		// HTTP Server headers should never be returned.
   355  		for headerName, headerValues := range resp.Header {
   356  			if strings.EqualFold(headerName, "Server") {
   357  				t.Logs.Errorf("Unexpected Server header=%v", headerValues)
   358  				return false
   359  			}
   360  		}
   361  		bodyStr := string(bodyRaw)
   362  		if !strings.Contains(bodyStr, "Hello World") {
   363  			t.Logs.Errorf("Unexpected response body=%v", bodyStr)
   364  			return false
   365  		}
   366  	} else {
   367  		if resp.StatusCode == http.StatusOK {
   368  			t.Logs.Errorf("Unexpected status code=%v", resp.StatusCode)
   369  			return false
   370  		}
   371  	}
   372  	return true
   373  }