github.com/verrazzano/verrazzano-monitoring-operator@v0.0.30/pkg/vmo/metricsexporter_test.go (about)

     1  // Copyright (c) 2020, 2021, 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 vmo
     5  
     6  import (
     7  	"strconv"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/prometheus/client_golang/prometheus"
    12  	"github.com/prometheus/client_golang/prometheus/testutil"
    13  	"github.com/stretchr/testify/assert"
    14  	vmctl "github.com/verrazzano/verrazzano-monitoring-operator/pkg/apis/vmcontroller/v1"
    15  	vmofake "github.com/verrazzano/verrazzano-monitoring-operator/pkg/client/clientset/versioned/fake"
    16  	"github.com/verrazzano/verrazzano-monitoring-operator/pkg/config"
    17  	"github.com/verrazzano/verrazzano-monitoring-operator/pkg/constants"
    18  	"github.com/verrazzano/verrazzano-monitoring-operator/pkg/metricsexporter"
    19  	"github.com/verrazzano/verrazzano-monitoring-operator/pkg/opensearch"
    20  	"github.com/verrazzano/verrazzano-monitoring-operator/pkg/resources"
    21  	"github.com/verrazzano/verrazzano-monitoring-operator/pkg/resources/configmaps"
    22  	"github.com/verrazzano/verrazzano-monitoring-operator/pkg/upgrade"
    23  	"github.com/verrazzano/verrazzano-monitoring-operator/pkg/util/logs/vzlog"
    24  	apiextensionsclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
    25  	kubeinformers "k8s.io/client-go/informers"
    26  	fake "k8s.io/client-go/kubernetes/fake"
    27  	"k8s.io/client-go/rest"
    28  	"k8s.io/client-go/util/workqueue"
    29  )
    30  
    31  type registerTest struct {
    32  	name               string
    33  	isConcurrent       bool
    34  	waitTime           time.Duration
    35  	allMetricsLength   int
    36  	failedMetricLength int
    37  	allMetrics         []prometheus.Collector
    38  }
    39  
    40  var allMetrics = metricsexporter.TestDelegate.GetAllMetricsArray()
    41  var delegate = metricsexporter.TestDelegate
    42  
    43  // TestInitializeAllMetricsArray tests that the metrics maps are added to the allmetrics array
    44  // GIVEN populated metrics maps
    45  //
    46  //	WHEN I call initializeAllMetricsArray
    47  //	THEN all the needed metrics are placed in the allmetrics array
    48  func TestInitializeAllMetricsArray(t *testing.T) {
    49  	clearMetrics()
    50  	assert := assert.New(t)
    51  	metricsexporter.TestDelegate.InitializeAllMetricsArray()
    52  	//This number should correspond to the number of total metrics, including metrics inside of metric maps
    53  	assert.Equal(30, len(*allMetrics), "There may be new metrics in the map, or some metrics may not be added to the allmetrics array from the metrics maps")
    54  }
    55  
    56  // TestNoMetrics, TestValid & TestInvalid tests that metrics in the allmetrics array are registered and failedMetrics are retried
    57  // GIVEN a populated allMetrics array
    58  //
    59  //	WHEN I call registerMetricsHandlers
    60  //	THEN all the valid metrics are registered and failedMetrics are retried
    61  func TestRegistrationSystem(t *testing.T) {
    62  	testCases := []registerTest{
    63  		{
    64  			name:               "TestNoMetrics",
    65  			isConcurrent:       false,
    66  			allMetrics:         []prometheus.Collector{},
    67  			allMetricsLength:   0,
    68  			failedMetricLength: 0,
    69  			waitTime:           0 * time.Second,
    70  		},
    71  		{
    72  			name:         "TestOneValidMetric",
    73  			isConcurrent: false,
    74  			allMetrics: []prometheus.Collector{
    75  				prometheus.NewCounter(prometheus.CounterOpts{Name: "testOneValidMetric_A", Help: "This is the first valid metric"}),
    76  			},
    77  			allMetricsLength:   1,
    78  			failedMetricLength: 0,
    79  			waitTime:           0 * time.Second,
    80  		},
    81  		{
    82  			name:         "TestOneInvalidMetric",
    83  			isConcurrent: true,
    84  			allMetrics: []prometheus.Collector{
    85  				prometheus.NewCounter(prometheus.CounterOpts{Help: "This is the first invalid metric"}),
    86  			},
    87  			allMetricsLength:   1,
    88  			failedMetricLength: 1,
    89  			waitTime:           1 * time.Second,
    90  		},
    91  		{
    92  			name:         "TestTwoValidMetrics",
    93  			isConcurrent: false,
    94  			allMetrics: []prometheus.Collector{
    95  				prometheus.NewCounter(prometheus.CounterOpts{Name: "TestTwoValidMetrics_A", Help: "This is the first valid metric"}),
    96  				prometheus.NewCounter(prometheus.CounterOpts{Name: "TestTwoValidMetrics_B", Help: "This is the second valid metric"}),
    97  			},
    98  			allMetricsLength:   2,
    99  			failedMetricLength: 0,
   100  			waitTime:           0 * time.Second,
   101  		},
   102  		{
   103  			name:         "TestTwoInvalidMetrics",
   104  			isConcurrent: true,
   105  			allMetrics: []prometheus.Collector{
   106  				prometheus.NewCounter(prometheus.CounterOpts{Help: "This is the first invalid metric"}),
   107  				prometheus.NewCounter(prometheus.CounterOpts{Help: "This is the second invalid metric"}),
   108  			},
   109  			allMetricsLength:   2,
   110  			failedMetricLength: 2,
   111  			waitTime:           1 * time.Second,
   112  		},
   113  		{
   114  			name:         "TestThreeValidMetrics",
   115  			isConcurrent: false,
   116  			allMetrics: []prometheus.Collector{
   117  				prometheus.NewCounter(prometheus.CounterOpts{Name: "TestThreeValidMetrics_A", Help: "This is the first valid metric"}),
   118  				prometheus.NewCounter(prometheus.CounterOpts{Name: "TestThreeValidMetrics_B", Help: "This is the second valid metric"}),
   119  				prometheus.NewCounter(prometheus.CounterOpts{Name: "TestThreeValidMetrics_C", Help: "This is the third valid metric"}),
   120  			},
   121  			allMetricsLength:   3,
   122  			failedMetricLength: 0,
   123  			waitTime:           0 * time.Second,
   124  		},
   125  		{
   126  			name:         "TestThreeInvalidMetrics",
   127  			isConcurrent: true,
   128  			allMetrics: []prometheus.Collector{
   129  				prometheus.NewCounter(prometheus.CounterOpts{Help: "This is the first invalid metric"}),
   130  				prometheus.NewCounter(prometheus.CounterOpts{Help: "This is the second invalid metric"}),
   131  				prometheus.NewCounter(prometheus.CounterOpts{Help: "This is the third invalid metric"}),
   132  			},
   133  			allMetricsLength:   3,
   134  			failedMetricLength: 3,
   135  			waitTime:           1 * time.Second,
   136  		},
   137  	}
   138  
   139  	for _, testCase := range testCases {
   140  		t.Run(testCase.name, func(t *testing.T) {
   141  			clearMetrics()
   142  			assert := assert.New(t)
   143  			*allMetrics = testCase.allMetrics
   144  			if !testCase.isConcurrent {
   145  				metricsexporter.TestDelegate.RegisterMetricsHandlers()
   146  			} else {
   147  				go metricsexporter.TestDelegate.RegisterMetricsHandlers()
   148  				time.Sleep(testCase.waitTime)
   149  			}
   150  			assert.Equal(testCase.allMetricsLength, len(*allMetrics), "allMetrics array length is not correct")
   151  			assert.Equal(testCase.failedMetricLength, len(delegate.GetFailedMetricsMap()), "failedMetrics map lenght is not correct")
   152  		})
   153  	}
   154  }
   155  
   156  func createControllerForTesting() (*Controller, *vmctl.VerrazzanoMonitoringInstance) {
   157  	const configMapName = "myDatasourcesConfigMap"
   158  
   159  	// GIVEN a Grafana datasources configmap exists and the Prometheus URL is the legacy URL
   160  	//  WHEN we call the createUpdateDatasourcesConfigMap
   161  	//  THEN the configmap is updated and the Prometheus URL points to the new Prometheus instance
   162  	vmo := &vmctl.VerrazzanoMonitoringInstance{}
   163  	vmo.Name = constants.VMODefaultName
   164  	vmo.Namespace = constants.VerrazzanoSystemNamespace
   165  
   166  	// set the Prometheus URL to the legacy URL
   167  	replaceMap := map[string]string{constants.GrafanaTmplPrometheusURI: resources.GetMetaName(vmo.Name, config.Prometheus.Name),
   168  		constants.GrafanaTmplAlertManagerURI: ""}
   169  	dataSourceTemplate, _ := asDashboardTemplate(constants.DataSourcesTmpl, replaceMap)
   170  
   171  	cm := configmaps.NewConfig(vmo, configMapName, map[string]string{datasourceYAMLKey: dataSourceTemplate})
   172  
   173  	cfg := &rest.Config{}
   174  	kubeextclientset, _ := apiextensionsclient.NewForConfig(cfg)
   175  
   176  	client := fake.NewSimpleClientset(cm)
   177  	defaultReplicasNum := 0
   178  	vmo.Labels = make(map[string]string)
   179  	statefulSetLister := kubeinformers.NewSharedInformerFactory(fake.NewSimpleClientset(), constants.ResyncPeriod).Apps().V1().StatefulSets().Lister()
   180  	controller := &Controller{
   181  		kubeclientset:    client,
   182  		kubeextclientset: kubeextclientset,
   183  		configMapLister:  &simpleConfigMapLister{kubeClient: client},
   184  		secretLister:     &simpleSecretLister{kubeClient: client},
   185  		log:              vzlog.DefaultLogger(),
   186  		operatorConfig: &config.OperatorConfig{
   187  			EnvName:                        "",
   188  			DefaultIngressTargetDNSName:    "",
   189  			DefaultSimpleComponentReplicas: &defaultReplicasNum,
   190  			MetricsPort:                    &defaultReplicasNum,
   191  			NatGatewayIPs:                  []string{},
   192  			Pvcs: config.Pvcs{
   193  				StorageClass:   "",
   194  				ZoneMatchLabel: "",
   195  			},
   196  		},
   197  		indexUpgradeMonitor: &upgrade.Monitor{},
   198  		clusterRoleLister:   kubeinformers.NewSharedInformerFactory(fake.NewSimpleClientset(), constants.ResyncPeriod).Rbac().V1().ClusterRoles().Lister(),
   199  		serviceLister:       kubeinformers.NewSharedInformerFactory(fake.NewSimpleClientset(), constants.ResyncPeriod).Core().V1().Services().Lister(),
   200  		storageClassLister:  kubeinformers.NewSharedInformerFactory(fake.NewSimpleClientset(), constants.ResyncPeriod).Storage().V1().StorageClasses().Lister(),
   201  		nodeLister:          kubeinformers.NewSharedInformerFactory(fake.NewSimpleClientset(), constants.ResyncPeriod).Core().V1().Nodes().Lister(),
   202  		deploymentLister:    kubeinformers.NewSharedInformerFactory(fake.NewSimpleClientset(), constants.ResyncPeriod).Apps().V1().Deployments().Lister(),
   203  		pvcLister:           kubeinformers.NewSharedInformerFactory(fake.NewSimpleClientset(), constants.ResyncPeriod).Core().V1().PersistentVolumeClaims().Lister(),
   204  		statefulSetLister:   statefulSetLister,
   205  		ingressLister:       kubeinformers.NewSharedInformerFactory(fake.NewSimpleClientset(), constants.ResyncPeriod).Networking().V1().Ingresses().Lister(),
   206  		vmoclientset:        vmofake.NewSimpleClientset(),
   207  		workqueue:           workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "VMOs"),
   208  		osClient:            opensearch.NewOSClient(statefulSetLister),
   209  	}
   210  	_ = createUpdateDatasourcesConfigMap(controller, vmo, configMapName, map[string]string{})
   211  
   212  	return controller, vmo
   213  }
   214  
   215  // TestReconcileMetrics tests that the FunctionMetrics methods record metrics properly when the reconcile function is called
   216  // GIVEN a FunctionMetric corresponding to the reconcile function
   217  //
   218  //	WHEN I call reconcile
   219  //	THEN the metrics for the reconcile function are to be captured
   220  func TestReconcileAndUpdateMetrics(t *testing.T) {
   221  
   222  	controller, vmo := createControllerForTesting()
   223  
   224  	metricsexporter.DefaultLabelFunction = func(idx int64) string { return "1" }
   225  	previousCount := testutil.ToFloat64(delegate.GetFunctionCounterMetric(metricsexporter.NamesReconcile))
   226  	previousUpdateCount := testutil.ToFloat64(delegate.GetCounterMetric(metricsexporter.NamesVMOUpdate))
   227  
   228  	controller.syncHandlerStandardMode(vmo)
   229  
   230  	newTimeStamp := testutil.ToFloat64(delegate.GetFunctionTimestampMetric(metricsexporter.NamesReconcile).WithLabelValues("1"))
   231  	newErrorCount := testutil.ToFloat64(delegate.GetFunctionErrorMetric(metricsexporter.NamesReconcile).WithLabelValues("1"))
   232  	newCount := testutil.ToFloat64(delegate.GetFunctionCounterMetric(metricsexporter.NamesReconcile))
   233  	newUpdateCount := testutil.ToFloat64(delegate.GetCounterMetric(metricsexporter.NamesVMOUpdate))
   234  
   235  	assert.Equal(t, previousCount, float64(newCount-1))
   236  	assert.Equal(t, newErrorCount, float64(1))
   237  	assert.Equal(t, previousUpdateCount, float64(newUpdateCount-1))
   238  	assert.LessOrEqual(t, int64(newTimeStamp*10)/10, time.Now().Unix())
   239  }
   240  
   241  // TestDeploymentMetrics tests that the FunctionMetrics methods record metrics properly when the createDeployment function is called
   242  // GIVEN a FunctionMetric corresponding to the deployment function
   243  //
   244  //	WHEN I call createDeployments
   245  //	THEN the metrics for the CreateDeployments function are to be captured, with the exception of (trivial) error metrics
   246  func TestDeploymentMetrics(t *testing.T) {
   247  
   248  	controller, vmo := createControllerForTesting()
   249  
   250  	metricsexporter.DefaultLabelFunction = func(idx int64) string { return "1" }
   251  	previousCount := testutil.ToFloat64(delegate.GetFunctionCounterMetric(metricsexporter.NamesDeployment))
   252  
   253  	CreateDeployments(controller, vmo, map[string]string{}, true)
   254  
   255  	newTimeStamp := testutil.ToFloat64(delegate.GetFunctionTimestampMetric(metricsexporter.NamesDeployment).WithLabelValues("1"))
   256  	newCount := testutil.ToFloat64(delegate.GetFunctionCounterMetric(metricsexporter.NamesDeployment))
   257  	//The error is incremented outside of the deployment function, it is quite trivial
   258  
   259  	assert.Equal(t, previousCount, float64(newCount-1))
   260  	assert.LessOrEqual(t, int64(newTimeStamp*10)/10, time.Now().Unix())
   261  }
   262  
   263  func TestIngressMetrics(t *testing.T) {
   264  	controller, vmo := createControllerForTesting()
   265  	metricsexporter.DefaultLabelFunction = func(idx int64) string { return "1" }
   266  	previousCount := testutil.ToFloat64(delegate.GetFunctionCounterMetric(metricsexporter.NamesIngress))
   267  	CreateIngresses(controller, vmo)
   268  	newTimeStamp := testutil.ToFloat64(delegate.GetFunctionTimestampMetric(metricsexporter.NamesIngress).WithLabelValues("1"))
   269  	newCount := testutil.ToFloat64(delegate.GetFunctionCounterMetric(metricsexporter.NamesIngress))
   270  	assert.Equal(t, previousCount, float64(newCount-1))
   271  	assert.LessOrEqual(t, int64(newTimeStamp*10)/10, time.Now().Unix())
   272  }
   273  
   274  func TestRoleBindingMetrics(t *testing.T) {
   275  	controller, vmo := createControllerForTesting()
   276  	previousCount := testutil.ToFloat64(delegate.GetCounterMetric(metricsexporter.NamesRoleBindings))
   277  	CreateRoleBindings(controller, vmo)
   278  	newCount := testutil.ToFloat64(delegate.GetCounterMetric(metricsexporter.NamesRoleBindings))
   279  	assert.Equal(t, previousCount, float64(newCount-1))
   280  }
   281  
   282  func TestThreadingMetrics(t *testing.T) {
   283  	controller, _ := createControllerForTesting()
   284  	gauge := delegate.GetGaugeMetrics(metricsexporter.NamesQueue)
   285  	gauge.Set(100)
   286  	controller.IsHealthy()
   287  	newCount := testutil.ToFloat64(gauge)
   288  	assert.Equal(t, 0, int(newCount))
   289  }
   290  
   291  func TestConfigMapMetrics(t *testing.T) {
   292  	controller, vmo := createControllerForTesting()
   293  	previousCount := testutil.ToFloat64(delegate.GetCounterMetric(metricsexporter.NamesConfigMap))
   294  	CreateConfigmaps(controller, vmo)
   295  	newCount := testutil.ToFloat64(delegate.GetCounterMetric(metricsexporter.NamesConfigMap))
   296  	newTimeStamp := testutil.ToFloat64(delegate.GetFunctionTimestampMetric(metricsexporter.NamesIngress).WithLabelValues(strconv.FormatInt(int64(previousCount)+1, 10)))
   297  	assert.Equal(t, previousCount, float64(newCount-1))
   298  	assert.LessOrEqual(t, int64(newTimeStamp*10)/10, time.Now().Unix())
   299  }
   300  func TestServiceMetrics(t *testing.T) {
   301  	clearMetrics()
   302  	controller, vmo := createControllerForTesting()
   303  	previousCount := testutil.ToFloat64(delegate.GetCounterMetric(metricsexporter.NamesServices))
   304  	CreateServices(controller, vmo)
   305  	newCount := testutil.ToFloat64(delegate.GetCounterMetric(metricsexporter.NamesServices))
   306  	newServicesCreated := testutil.ToFloat64(delegate.GetCounterMetric(metricsexporter.NamesServicesCreated))
   307  	newTimeStamp := testutil.ToFloat64(delegate.GetTimestampMetric(metricsexporter.NamesServices).WithLabelValues(strconv.FormatInt(int64(previousCount)+1, 10)))
   308  	assert.Equal(t, previousCount, float64(newCount-1))
   309  	assert.EqualValues(t, 1, newServicesCreated)
   310  	assert.LessOrEqual(t, int64(newTimeStamp*10)/10, time.Now().Unix())
   311  }
   312  
   313  // helper function to ensure consistency between tests
   314  func clearMetrics() {
   315  	*allMetrics = []prometheus.Collector{}
   316  	for c := range metricsexporter.TestDelegate.GetFailedMetricsMap() {
   317  		delete(metricsexporter.TestDelegate.GetFailedMetricsMap(), c) //maps are references, hence we can delete like normal here
   318  	}
   319  	time.Sleep(time.Second * 1)
   320  	metricsexporter.RequiredInitialization()
   321  }