github.com/verrazzano/verrazzano@v1.7.1/tests/e2e/metrics/deploymetrics/deploymetrics_test.go (about)

     1  // Copyright (c) 2021, 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 deploymetrics
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	dump "github.com/verrazzano/verrazzano/tests/e2e/pkg/test/clusterdump"
    10  	"os"
    11  	"time"
    12  
    13  	"github.com/verrazzano/verrazzano/pkg/k8s/resource"
    14  
    15  	. "github.com/onsi/ginkgo/v2"
    16  	. "github.com/onsi/gomega"
    17  	promoperapi "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1"
    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  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    25  	"k8s.io/apimachinery/pkg/runtime"
    26  	k8sclient "sigs.k8s.io/controller-runtime/pkg/client"
    27  )
    28  
    29  const (
    30  	deploymetricsCompYaml   = "testdata/deploymetrics/deploymetrics-comp.yaml"
    31  	deploymetricsCompName   = "deploymetrics-deployment"
    32  	deploymetricsAppYaml    = "testdata/deploymetrics/deploymetrics-app.yaml"
    33  	deploymetricsAppName    = "deploymetrics-appconf"
    34  	skipVerifications       = "Skip Verifications"
    35  	errGenerateSvcMonJobFmt = "Failed to generate the Service Monitor job name: %v"
    36  )
    37  
    38  var (
    39  	expectedPodsDeploymetricsApp = []string{"deploymetrics-workload"}
    40  	generatedNamespace           = pkg.GenerateNamespace("deploymetrics")
    41  	metricsTest                  pkg.MetricsTest
    42  
    43  	waitTimeout              = 10 * time.Minute
    44  	pollingInterval          = 30 * time.Second
    45  	shortPollingInterval     = 10 * time.Second
    46  	shortWaitTimeout         = 5 * time.Minute
    47  	longWaitTimeout          = 15 * time.Minute
    48  	longPollingInterval      = 30 * time.Second
    49  	imagePullWaitTimeout     = 40 * time.Minute
    50  	imagePullPollingInterval = 30 * time.Second
    51  
    52  	clusterNameLabel string
    53  
    54  	t = framework.NewTestFramework("deploymetrics")
    55  )
    56  
    57  var clusterDump = dump.NewClusterDumpWrapper(t, generatedNamespace)
    58  var kubeconfig string
    59  var beforeSuite = clusterDump.BeforeSuiteFunc(func() {
    60  	if !skipDeploy {
    61  		deployMetricsApplication()
    62  	}
    63  	var err error
    64  
    65  	kubeconfig, err = getKubeConfigPath()
    66  	if err != nil {
    67  		pkg.Log(pkg.Error, fmt.Sprintf("Failed to get the kubeconfig: %v", err))
    68  	}
    69  	clusterNameLabel, err = pkg.GetClusterNameMetricLabel(kubeconfig)
    70  	if err != nil {
    71  		pkg.Log(pkg.Error, err.Error())
    72  		Fail(err.Error())
    73  	}
    74  
    75  	// Create a service in VZ versions 1.4.0 and greater, so that a servicemonitor will be generated
    76  	// by Verrazzano for Prometheus Operator
    77  	isVzMinVer14, _ := pkg.IsVerrazzanoMinVersion("1.4.0", kubeconfig)
    78  	if isVzMinVer14 {
    79  		Eventually(func() error {
    80  			promJobName, err := getPromJobName()
    81  			if err != nil {
    82  				pkg.Log(pkg.Error, fmt.Sprintf(errGenerateSvcMonJobFmt, err))
    83  			}
    84  			serviceName := promJobName
    85  			err = createService(serviceName)
    86  			// if this is running post upgrade, we may have already run this test pre-upgrade and
    87  			// created the service. It is not an error if the service already exists.
    88  			if err != nil && !errors.IsAlreadyExists(err) {
    89  				pkg.Log(pkg.Error, fmt.Sprintf("Failed to create the Service for the Service Monitor: %v", err))
    90  				return err
    91  			}
    92  			return nil
    93  		}, waitTimeout, pollingInterval).Should(BeNil(), "Expected to be able to create the metrics service")
    94  	}
    95  
    96  	metricsTest, err = pkg.NewMetricsTest(kubeconfig, map[string]string{})
    97  	if err != nil {
    98  		AbortSuite(fmt.Sprintf("Failed to create the Metrics test object: %v", err))
    99  	}
   100  })
   101  var _ = clusterDump.AfterEach(func() {})             // Dump cluster if spec fails
   102  var afterSuite = clusterDump.AfterSuiteFunc(func() { // Dump cluster if aftersuite fails
   103  	if !skipUndeploy {
   104  		undeployMetricsApplication()
   105  	}
   106  })
   107  
   108  var _ = BeforeSuite(beforeSuite)
   109  var _ = AfterSuite(afterSuite)
   110  
   111  func deployMetricsApplication() {
   112  	t.Logs.Info("Deploy DeployMetrics Application")
   113  
   114  	t.Logs.Info("Create namespace")
   115  	start := time.Now()
   116  	Eventually(func() (*v1.Namespace, error) {
   117  		nsLabels := map[string]string{
   118  			"verrazzano-managed": "true",
   119  			"istio-injection":    istioInjection}
   120  		return pkg.CreateNamespace(namespace, nsLabels)
   121  	}, shortWaitTimeout, shortPollingInterval).ShouldNot(BeNil())
   122  
   123  	t.Logs.Info("Create component resource")
   124  	Eventually(func() error {
   125  		file, err := pkg.FindTestDataFile(deploymetricsCompYaml)
   126  		if err != nil {
   127  			return err
   128  		}
   129  		return resource.CreateOrUpdateResourceFromFileInGeneratedNamespace(file, namespace)
   130  	}, shortWaitTimeout, shortPollingInterval).ShouldNot(HaveOccurred())
   131  
   132  	t.Logs.Info("Create application resource")
   133  	Eventually(func() error {
   134  		file, err := pkg.FindTestDataFile(deploymetricsAppYaml)
   135  		if err != nil {
   136  			return err
   137  		}
   138  		return resource.CreateOrUpdateResourceFromFileInGeneratedNamespace(file, namespace)
   139  	}, shortWaitTimeout, shortPollingInterval).ShouldNot(HaveOccurred(), "Failed to create DeployMetrics application resource")
   140  
   141  	Eventually(func() bool {
   142  		return pkg.ContainerImagePullWait(namespace, expectedPodsDeploymetricsApp)
   143  	}, imagePullWaitTimeout, imagePullPollingInterval).Should(BeTrue())
   144  
   145  	t.Logs.Info("Verify deploymetrics-workload pod is running")
   146  	Eventually(func() bool {
   147  		result, err := pkg.PodsRunning(namespace, expectedPodsDeploymetricsApp)
   148  		if err != nil {
   149  			AbortSuite(fmt.Sprintf("One or more pods are not running in the namespace: %v, error: %v", namespace, err))
   150  		}
   151  		return result
   152  	}, waitTimeout, pollingInterval).Should(BeTrue())
   153  	metrics.Emit(t.Metrics.With("deployment_elapsed_time", time.Since(start).Milliseconds()))
   154  }
   155  
   156  func undeployMetricsApplication() {
   157  	t.Logs.Info("Undeploy DeployMetrics Application")
   158  
   159  	t.Logs.Info("Delete application")
   160  	start := time.Now()
   161  	Eventually(func() error {
   162  		file, err := pkg.FindTestDataFile(deploymetricsCompYaml)
   163  		if err != nil {
   164  			return err
   165  		}
   166  		return resource.DeleteResourceFromFileInGeneratedNamespace(file, namespace)
   167  	}, shortWaitTimeout, shortPollingInterval).ShouldNot(HaveOccurred())
   168  
   169  	t.Logs.Info("Delete components")
   170  	Eventually(func() error {
   171  		file, err := pkg.FindTestDataFile(deploymetricsAppYaml)
   172  		if err != nil {
   173  			return err
   174  		}
   175  		return resource.DeleteResourceFromFileInGeneratedNamespace(file, namespace)
   176  	}, shortWaitTimeout, shortPollingInterval).ShouldNot(HaveOccurred())
   177  
   178  	t.Logs.Info("Wait for pods to terminate")
   179  	Eventually(func() bool {
   180  		podsNotRunning, _ := pkg.PodsNotRunning(namespace, expectedPodsDeploymetricsApp)
   181  		return podsNotRunning
   182  	}, shortWaitTimeout, shortPollingInterval).Should(BeTrue())
   183  
   184  	isVzMinVer14, _ := pkg.IsVerrazzanoMinVersion("1.4.0", kubeconfig)
   185  	if isVzMinVer14 {
   186  		Eventually(func() bool {
   187  			serviceName, err := getPromJobName()
   188  			if err != nil {
   189  				pkg.Log(pkg.Error, fmt.Sprintf(errGenerateSvcMonJobFmt, err))
   190  				return false
   191  			}
   192  			_, err = pkg.GetServiceMonitor(namespace, serviceName)
   193  			return err != nil
   194  		}, waitTimeout, pollingInterval).Should(BeTrue(), "Expected Service Monitor to not exist")
   195  	}
   196  	t.Logs.Info("Delete namespace")
   197  	Eventually(func() error {
   198  		return pkg.DeleteNamespace(namespace)
   199  	}, longWaitTimeout, longPollingInterval).ShouldNot(HaveOccurred())
   200  
   201  	t.Logs.Info("Wait for Finalizer to be removed")
   202  	Eventually(func() bool {
   203  		return pkg.CheckNamespaceFinalizerRemoved(namespace)
   204  	}, shortWaitTimeout, shortPollingInterval).Should(BeTrue())
   205  
   206  	t.Logs.Info("Waiting for namespace deletion")
   207  	Eventually(func() bool {
   208  		_, err := pkg.GetNamespace(namespace)
   209  		return err != nil && errors.IsNotFound(err)
   210  	}, longWaitTimeout, longPollingInterval).Should(BeTrue())
   211  	metrics.Emit(t.Metrics.With("undeployment_elapsed_time", time.Since(start).Milliseconds()))
   212  }
   213  
   214  var _ = t.Describe("DeployMetrics Application test", Label("f:app-lcm.oam"), func() {
   215  
   216  	t.Context("for Prometheus Config.", Label("f:observability.monitoring.prom"), func() {
   217  		t.It("Verify that Prometheus Service Monitor exists", func() {
   218  			if skipVerify {
   219  				Skip(skipVerifications)
   220  			}
   221  			isVzMinVer14, _ := pkg.IsVerrazzanoMinVersion("1.4.0", kubeconfig)
   222  			if isVzMinVer14 {
   223  				serviceName, err := getPromJobName()
   224  				if err != nil {
   225  					pkg.Log(pkg.Error, fmt.Sprintf(errGenerateSvcMonJobFmt, err))
   226  				}
   227  				Eventually(func() (*promoperapi.ServiceMonitor, error) {
   228  					monitor, err := pkg.GetServiceMonitor(namespace, serviceName)
   229  					if err != nil {
   230  						pkg.Log(pkg.Error, fmt.Sprintf("Failed to get the Service Monitor from the cluster: %v", err))
   231  					}
   232  					return monitor, err
   233  				}, waitTimeout, pollingInterval).Should(Not(BeNil()), "Expected to find Service Monitor")
   234  			}
   235  		})
   236  	})
   237  
   238  	t.Context("Retrieve Prometheus scraped metrics for", Label("f:observability.monitoring.prom"), func() {
   239  		t.It("App Component", func() {
   240  			if skipVerify {
   241  				Skip(skipVerifications)
   242  			}
   243  			clusterName, err := getClusterNameForPromQuery()
   244  			if err != nil {
   245  				pkg.Log(pkg.Error, fmt.Sprintf("Failed getting the cluster name: %v", err))
   246  			}
   247  			metricLabels := map[string]string{
   248  				"app_oam_dev_name": "deploymetrics-appconf",
   249  				clusterNameLabel:   clusterName,
   250  			}
   251  			Eventually(func() bool {
   252  				return metricsTest.MetricsExist("http_server_requests_seconds_count", metricLabels)
   253  			}, longWaitTimeout, longPollingInterval).Should(BeTrue(), "Expected to find Prometheus scraped metrics for App Component.")
   254  		})
   255  		t.It("App Config", func() {
   256  			if skipVerify {
   257  				Skip(skipVerifications)
   258  			}
   259  			clusterName, err := getClusterNameForPromQuery()
   260  			if err != nil {
   261  				pkg.Log(pkg.Error, fmt.Sprintf("Failed getting the cluster name: %v", err))
   262  			}
   263  			metricLabels := map[string]string{
   264  				"app_oam_dev_component": "deploymetrics-deployment",
   265  				clusterNameLabel:        clusterName,
   266  			}
   267  			Eventually(func() bool {
   268  				return metricsTest.MetricsExist("tomcat_sessions_created_sessions_total", metricLabels)
   269  			}, longWaitTimeout, longPollingInterval).Should(BeTrue(), "Expected to find Prometheus scraped metrics for App Config.")
   270  		})
   271  	})
   272  
   273  })
   274  
   275  // Return the cluster name used for the Prometheus query
   276  func getClusterNameForPromQuery() (string, error) {
   277  	if pkg.IsManagedClusterProfile() {
   278  		var clusterName = os.Getenv("CLUSTER_NAME")
   279  		pkg.Log(pkg.Info, "This is a managed cluster, returning cluster name - "+clusterName)
   280  		return clusterName, nil
   281  	}
   282  	kubeConfigPath, err := k8sutil.GetKubeConfigLocation()
   283  	if err != nil {
   284  		return kubeConfigPath, err
   285  	}
   286  	isMinVersion110, err := pkg.IsVerrazzanoMinVersion("1.1.0", kubeConfigPath)
   287  	if err != nil {
   288  		return "", err
   289  	}
   290  	if isMinVersion110 {
   291  		return "local", nil
   292  	}
   293  	return "", nil
   294  }
   295  
   296  func getDefaultKubeConfigPath() (string, error) {
   297  	kubeConfigPath, err := k8sutil.GetKubeConfigLocation()
   298  	if err != nil {
   299  		return kubeConfigPath, err
   300  	}
   301  	pkg.Log(pkg.Info, "Default kube config path - "+kubeConfigPath)
   302  	return kubeConfigPath, nil
   303  }
   304  
   305  func getKubeConfigPath() (string, error) {
   306  	adminKubeConfig, present := os.LookupEnv("ADMIN_KUBECONFIG")
   307  	if pkg.IsManagedClusterProfile() {
   308  		if !present {
   309  			return adminKubeConfig, fmt.Errorf("Environment variable ADMIN_KUBECONFIG is required to run the test")
   310  		}
   311  	} else {
   312  		return getDefaultKubeConfigPath()
   313  	}
   314  	return adminKubeConfig, nil
   315  }
   316  
   317  func getPromJobName() (string, error) {
   318  	kubeconfig, err := getKubeConfigPath()
   319  	if err != nil {
   320  		return kubeconfig, err
   321  	}
   322  	usesServiceMonitor, err := pkg.IsVerrazzanoMinVersion("1.4.0", kubeconfig)
   323  	if err != nil {
   324  		return kubeconfig, err
   325  	}
   326  	if usesServiceMonitor {
   327  		return pkg.GetAppServiceMonitorName(namespace, deploymetricsAppName, "deploymetrics-deployment"), nil
   328  	}
   329  	// For VZ versions prior to 1.4.0, the job name in prometheus scrape config was of the old format
   330  	// <app_name>_default_<app_namespace>_<app_component_name>
   331  	return fmt.Sprintf("%s_default_%s_%s", deploymetricsAppName, generatedNamespace, deploymetricsCompName), nil
   332  }
   333  
   334  // create Service creates a service for metrics collection
   335  func createService(name string) error {
   336  	config, err := k8sutil.GetKubeConfig()
   337  	if err != nil {
   338  		return err
   339  	}
   340  	scheme := runtime.NewScheme()
   341  	_ = v1.AddToScheme(scheme)
   342  	cli, err := k8sclient.New(config, k8sclient.Options{Scheme: scheme})
   343  	if err != nil {
   344  		return err
   345  	}
   346  
   347  	svc := v1.Service{
   348  		ObjectMeta: metav1.ObjectMeta{
   349  			Namespace: namespace,
   350  			Name:      name,
   351  		},
   352  		Spec: v1.ServiceSpec{
   353  			Selector: map[string]string{
   354  				"app": "deploymetrics",
   355  			},
   356  			Ports: []v1.ServicePort{
   357  				{
   358  					Name:     "metrics",
   359  					Port:     8080,
   360  					Protocol: "TCP",
   361  				},
   362  			},
   363  		},
   364  	}
   365  	return cli.Create(context.TODO(), &svc)
   366  }