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 }