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 }