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 }