github.com/verrazzano/verrazzano@v1.7.1/application-operator/controllers/metricsbinding/metricsbinding_update_test.go (about) 1 // Copyright (c) 2022, 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 metricsbinding 5 6 import ( 7 "context" 8 "strings" 9 "testing" 10 11 promoperapi "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1" 12 asserts "github.com/stretchr/testify/assert" 13 vzapi "github.com/verrazzano/verrazzano/application-operator/apis/app/v1alpha1" 14 "github.com/verrazzano/verrazzano/application-operator/constants" 15 vzconst "github.com/verrazzano/verrazzano/pkg/constants" 16 "github.com/verrazzano/verrazzano/pkg/log/vzlog" 17 "github.com/verrazzano/verrazzano/pkg/metricsutils" 18 appsv1 "k8s.io/api/apps/v1" 19 corev1 "k8s.io/api/core/v1" 20 "k8s.io/apimachinery/pkg/runtime" 21 "k8s.io/apimachinery/pkg/types" 22 "sigs.k8s.io/controller-runtime/pkg/client/fake" 23 ) 24 25 // TestGetMetricsTemplate tests the retrieval process of the metrics template 26 // GIVEN a metrics binding 27 // WHEN the function receives the binding 28 // THEN return the metrics template without error 29 func TestGetMetricsTemplate(t *testing.T) { 30 assert := asserts.New(t) 31 32 scheme := runtime.NewScheme() 33 _ = vzapi.AddToScheme(scheme) 34 c := fake.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(metricsTemplate).Build() 35 36 localMetricsBinding := metricsBinding.DeepCopy() 37 38 log := vzlog.DefaultLogger() 39 r := newReconciler(c) 40 template, err := r.getMetricsTemplate(context.Background(), localMetricsBinding, log) 41 assert.NoError(err, "Expected no error getting the MetricsTemplate from the MetricsBinding") 42 assert.NotNil(template) 43 } 44 45 // TestHandleDefaultMetricsTemplate tests the retrieval process of the metrics template 46 // GIVEN a metrics binding 47 // WHEN the function receives the binding 48 // THEN a scrape config gets generated for the target workload 49 func TestHandleDefaultMetricsTemplate(t *testing.T) { 50 assert := asserts.New(t) 51 52 scheme := runtime.NewScheme() 53 _ = vzapi.AddToScheme(scheme) 54 _ = corev1.AddToScheme(scheme) 55 _ = appsv1.AddToScheme(scheme) 56 _ = promoperapi.AddToScheme(scheme) 57 58 labeledNs := plainNs.DeepCopy() 59 labeledNs.Labels = map[string]string{constants.LabelIstioInjection: "enabled"} 60 61 labeledWorkload := plainWorkload.DeepCopy() 62 labeledWorkload.Labels = map[string]string{constants.MetricsWorkloadLabel: testDeploymentName} 63 64 serviceMonitorNSN := types.NamespacedName{Namespace: testMetricsBindingNamespace, Name: testMetricsBindingName} 65 66 tests := []struct { 67 name string 68 workload *appsv1.Deployment 69 namespace *corev1.Namespace 70 expectError bool 71 }{ 72 { 73 name: "test no workload", 74 workload: &appsv1.Deployment{}, 75 namespace: labeledNs, 76 expectError: true, 77 }, 78 { 79 name: "test no namespace", 80 workload: labeledWorkload, 81 namespace: &corev1.Namespace{}, 82 expectError: true, 83 }, 84 { 85 name: "test workload no label", 86 workload: plainWorkload, 87 namespace: labeledNs, 88 expectError: true, 89 }, 90 { 91 name: "test workload and namespace label", 92 workload: labeledWorkload, 93 namespace: labeledNs, 94 expectError: false, 95 }, 96 { 97 name: "test workload label only", 98 workload: labeledWorkload, 99 namespace: plainNs, 100 expectError: false, 101 }, 102 } 103 for _, tt := range tests { 104 t.Run(tt.name, func(t *testing.T) { 105 c := fake.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects([]runtime.Object{ 106 metricsTemplate, 107 tt.workload, 108 tt.namespace, 109 }...).Build() 110 111 localMetricsBinding := metricsBinding.DeepCopy() 112 113 log := vzlog.DefaultLogger() 114 r := newReconciler(c) 115 err := r.handleDefaultMetricsTemplate(context.Background(), localMetricsBinding, log) 116 if tt.expectError { 117 assert.Error(err, "Expected error handling the default MetricsTemplate") 118 return 119 } 120 assert.NoError(err, "Expected no error handling the default MetricsTemplate") 121 122 // Get the service monitor for analysis 123 serviceMonitor := promoperapi.ServiceMonitor{} 124 err = c.Get(context.TODO(), serviceMonitorNSN, &serviceMonitor) 125 assert.NoError(err, "Expected no error getting the Service Monitor") 126 127 assert.Equal(1, len(serviceMonitor.Spec.Endpoints)) 128 assert.Contains(serviceMonitor.Spec.Endpoints[0].RelabelConfigs[1].SourceLabels, promoperapi.LabelName(workloadSourceLabel)) 129 assert.Equal("local", serviceMonitor.Spec.Endpoints[0].RelabelConfigs[0].Replacement) 130 if _, ok := tt.namespace.Labels[constants.LabelIstioInjection]; ok { 131 assert.Equal("https", serviceMonitor.Spec.Endpoints[0].Scheme) 132 } else { 133 assert.Equal("http", serviceMonitor.Spec.Endpoints[0].Scheme) 134 } 135 }) 136 } 137 } 138 139 // TestHandleCustomMetricsTemplate tests the custom metrics path implementation 140 // GIVEN a metrics binding 141 // WHEN the function receives the binding 142 // THEN a scrape config gets generated for the target workload 143 func TestHandleCustomMetricsTemplate(t *testing.T) { 144 assert := asserts.New(t) 145 146 scheme := runtime.NewScheme() 147 _ = vzapi.AddToScheme(scheme) 148 _ = corev1.AddToScheme(scheme) 149 _ = appsv1.AddToScheme(scheme) 150 _ = promoperapi.AddToScheme(scheme) 151 152 labeledNs := plainNs.DeepCopy() 153 labeledNs.Labels = map[string]string{constants.LabelIstioInjection: "enabled"} 154 155 labeledWorkload := plainWorkload.DeepCopy() 156 labeledWorkload.Labels = map[string]string{constants.MetricsWorkloadLabel: testDeploymentName} 157 158 populatedTemplate, err := getTemplateTestFile() 159 assert.NoError(err) 160 testFileCMEmpty, err := getConfigMapFromTestFile(true) 161 assert.NoError(err) 162 testFileCMFilled, err := getConfigMapFromTestFile(false) 163 assert.NoError(err) 164 testFileCMOtherScrapeConfigs, err := readConfigMapData("testdata/cmDataHasOtherScrapeConfigs.yaml") 165 assert.NoError(err) 166 testFileSec, err := getSecretFromTestFile(true) 167 assert.NoError(err) 168 169 tests := []struct { 170 name string 171 workload *appsv1.Deployment 172 namespace *corev1.Namespace 173 configMap *corev1.ConfigMap 174 expectConfigMapAdd bool 175 secret *corev1.Secret 176 expectError bool 177 }{ 178 { 179 name: "test configmap empty", 180 workload: labeledWorkload, 181 namespace: labeledNs, 182 configMap: testFileCMEmpty, 183 expectError: false, 184 expectConfigMapAdd: true, 185 }, 186 { 187 name: "test configmap with other scrape configs", 188 workload: labeledWorkload, 189 namespace: labeledNs, 190 configMap: testFileCMOtherScrapeConfigs, 191 expectError: false, 192 expectConfigMapAdd: true, 193 }, 194 { 195 name: "test configmap filled", 196 workload: labeledWorkload, 197 namespace: labeledNs, 198 configMap: testFileCMFilled, 199 expectError: false, 200 expectConfigMapAdd: false, 201 }, 202 { 203 name: "test secret", 204 workload: labeledWorkload, 205 namespace: labeledNs, 206 secret: testFileSec, 207 expectError: false, 208 }, 209 { 210 name: "test configmap no Istio", 211 workload: labeledWorkload, 212 namespace: plainNs, 213 configMap: testFileCMEmpty, 214 expectError: false, 215 expectConfigMapAdd: true, 216 }, 217 { 218 name: "test secret no Istio", 219 workload: labeledWorkload, 220 namespace: plainNs, 221 secret: testFileSec, 222 expectError: false, 223 }, 224 } 225 for _, tt := range tests { 226 t.Run(tt.name, func(t *testing.T) { 227 c := fake.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects([]runtime.Object{ 228 populatedTemplate, 229 tt.workload, 230 tt.namespace, 231 }...) 232 233 localMetricsBinding := metricsBinding.DeepCopy() 234 configMapNumScrapeConfigs := 0 235 if tt.configMap != nil { 236 c = c.WithRuntimeObjects(tt.configMap) 237 parsedConfigMap, err := getConfigData(tt.configMap) 238 assert.NoError(err, "Could not parse test config map") 239 configMapNumScrapeConfigs = len(parsedConfigMap.Search(prometheusScrapeConfigsLabel).Children()) 240 } 241 if tt.secret != nil { 242 c = c.WithRuntimeObjects(tt.secret) 243 localMetricsBinding.Spec.PrometheusConfigMap = vzapi.NamespaceName{} 244 localMetricsBinding.Spec.PrometheusConfigSecret = vzapi.SecretKey{ 245 Namespace: vzconst.PrometheusOperatorNamespace, 246 Name: vzconst.PromAdditionalScrapeConfigsSecretName, 247 Key: vzconst.PromAdditionalScrapeConfigsSecretKey, 248 } 249 } 250 251 client := c.Build() 252 253 log := vzlog.DefaultLogger() 254 r := newReconciler(client) 255 err = r.handleCustomMetricsTemplate(context.TODO(), localMetricsBinding, log) 256 if tt.expectError { 257 assert.Error(err, "Expected error handling the custom MetricsTemplate") 258 return 259 } 260 assert.NoError(err, "Expected no error handling the custom MetricsTemplate") 261 262 if tt.configMap != nil { 263 var newCM corev1.ConfigMap 264 err := client.Get(context.TODO(), types.NamespacedName{Namespace: vzconst.VerrazzanoSystemNamespace, Name: testConfigMapName}, &newCM) 265 assert.NoError(err) 266 assert.True(strings.Contains(newCM.Data[prometheusConfigKey], createJobName(localMetricsBinding))) 267 parsedPrometheusConfig, err := getConfigData(&newCM) 268 assert.NoError(err) 269 newScrapeConfigs := parsedPrometheusConfig.Search(prometheusScrapeConfigsLabel) 270 assert.NotNil(newScrapeConfigs) 271 if tt.expectConfigMapAdd { 272 assert.Equal(configMapNumScrapeConfigs+1, len(newScrapeConfigs.Children())) 273 } else { 274 assert.Equal(configMapNumScrapeConfigs, len(newScrapeConfigs.Children())) 275 } 276 foundJob := metricsutils.FindScrapeJob(newScrapeConfigs, createJobName(localMetricsBinding)) 277 assert.NotNil(foundJob) 278 } 279 if tt.secret != nil { 280 var newSecret corev1.Secret 281 err := client.Get(context.TODO(), types.NamespacedName{Namespace: vzconst.PrometheusOperatorNamespace, Name: vzconst.PromAdditionalScrapeConfigsSecretName}, &newSecret) 282 assert.NoError(err) 283 assert.True(strings.Contains(string(newSecret.Data[vzconst.PromAdditionalScrapeConfigsSecretKey]), createJobName(localMetricsBinding))) 284 } 285 }) 286 } 287 } 288 289 // TestReconcileCreateOrUpdate tests the create or update process of the reconiler 290 // GIVEN a metrics binding 291 // WHEN the function receives the binding 292 // THEN the binding gets updated accordingly 293 func TestReconcileCreateOrUpdate(t *testing.T) { 294 assert := asserts.New(t) 295 296 scheme := runtime.NewScheme() 297 _ = vzapi.AddToScheme(scheme) 298 _ = corev1.AddToScheme(scheme) 299 _ = appsv1.AddToScheme(scheme) 300 _ = promoperapi.AddToScheme(scheme) 301 302 labeledNs := plainNs.DeepCopy() 303 labeledNs.Labels = map[string]string{constants.LabelIstioInjection: "enabled"} 304 305 labeledWorkload := plainWorkload.DeepCopy() 306 labeledWorkload.Labels = map[string]string{constants.MetricsWorkloadLabel: testDeploymentName} 307 308 populatedTemplate, err := getTemplateTestFile() 309 assert.NoError(err) 310 testFileCM, err := getConfigMapFromTestFile(true) 311 assert.NoError(err) 312 testFileSec, err := getSecretFromTestFile(true) 313 assert.NoError(err) 314 315 CMMetricsBinding := metricsBinding.DeepCopy() 316 secMetricsBinding := metricsBinding.DeepCopy() 317 secMetricsBinding.Spec.PrometheusConfigMap = vzapi.NamespaceName{} 318 secMetricsBinding.Spec.PrometheusConfigSecret = vzapi.SecretKey{ 319 Namespace: vzconst.PrometheusOperatorNamespace, 320 Name: vzconst.PromAdditionalScrapeConfigsSecretName, 321 Key: vzconst.PromAdditionalScrapeConfigsSecretKey, 322 } 323 legacyBinding := metricsBinding.DeepCopy() 324 legacyBinding.Spec.MetricsTemplate.Namespace = constants.LegacyDefaultMetricsTemplateNamespace 325 legacyBinding.Spec.MetricsTemplate.Name = constants.LegacyDefaultMetricsTemplateName 326 legacyBinding.Spec.PrometheusConfigMap.Namespace = vzconst.VerrazzanoSystemNamespace 327 legacyBinding.Spec.PrometheusConfigMap.Name = vzconst.VmiPromConfigName 328 329 legacyBindingCustomTemplateDefaultCM := metricsBinding.DeepCopy() 330 legacyBindingCustomTemplateDefaultCM.Spec.PrometheusConfigMap.Namespace = vzconst.VerrazzanoSystemNamespace 331 legacyBindingCustomTemplateDefaultCM.Spec.PrometheusConfigMap.Name = vzconst.VmiPromConfigName 332 333 tests := []struct { 334 name string 335 metricsBinding *vzapi.MetricsBinding 336 workload *appsv1.Deployment 337 namespace *corev1.Namespace 338 configMap *corev1.ConfigMap 339 secret *corev1.Secret 340 requeue bool 341 expectError bool 342 }{ 343 { 344 name: "test configmap", 345 metricsBinding: CMMetricsBinding, 346 workload: labeledWorkload, 347 namespace: labeledNs, 348 configMap: testFileCM, 349 requeue: true, 350 expectError: false, 351 }, 352 { 353 name: "test secret", 354 metricsBinding: secMetricsBinding, 355 workload: labeledWorkload, 356 namespace: labeledNs, 357 secret: testFileSec, 358 requeue: true, 359 expectError: false, 360 }, 361 { 362 name: "test legacy", 363 metricsBinding: legacyBinding, 364 workload: labeledWorkload, 365 namespace: labeledNs, 366 secret: testFileSec, 367 requeue: true, 368 expectError: false, 369 }, 370 { 371 name: "test legacy with custom template default CM", 372 metricsBinding: legacyBindingCustomTemplateDefaultCM, 373 workload: labeledWorkload, 374 namespace: labeledNs, 375 secret: testFileSec, 376 requeue: true, 377 expectError: false, 378 }, 379 } 380 for _, tt := range tests { 381 t.Run(tt.name, func(t *testing.T) { 382 c := fake.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects([]runtime.Object{ 383 populatedTemplate, 384 tt.workload, 385 tt.namespace, 386 tt.metricsBinding, 387 }...) 388 389 if tt.configMap != nil { 390 c = c.WithRuntimeObjects(tt.configMap) 391 } 392 if tt.secret != nil { 393 c = c.WithRuntimeObjects(tt.secret) 394 } 395 396 client := c.Build() 397 log := vzlog.DefaultLogger() 398 r := newReconciler(client) 399 reconcileMetricsBinding := tt.metricsBinding.DeepCopy() 400 result, err := r.reconcileBindingCreateOrUpdate(context.TODO(), reconcileMetricsBinding, log) 401 if tt.expectError { 402 assert.Error(err, "Expected error reconciling the Metrics Binding") 403 return 404 } 405 assert.NoError(err, "Expected no error reconciling the Metrics Binding") 406 assert.Equal(tt.requeue, result.Requeue) 407 408 if isLegacyDefaultMetricsBinding(tt.metricsBinding) { 409 newMB := vzapi.MetricsBinding{} 410 err := client.Get(context.TODO(), types.NamespacedName{Namespace: tt.metricsBinding.Namespace, Name: tt.metricsBinding.Name}, &newMB) 411 assert.Error(err, "Expected not to find the Metrics Binding in the cluster") 412 return 413 } 414 415 if isLegacyVmiPrometheusConfigMapName(tt.metricsBinding.Spec.PrometheusConfigMap) { 416 newMB := vzapi.MetricsBinding{} 417 err := client.Get(context.TODO(), types.NamespacedName{Namespace: tt.metricsBinding.Namespace, Name: tt.metricsBinding.Name}, &newMB) 418 assert.NoError(err) 419 420 // for legacy VMI config map in metrics binding, it should be updated to have 421 // the additional scrape configs secret, and the config map should be removed from 422 // the metrics binding 423 assert.Empty(newMB.Spec.PrometheusConfigMap.Namespace) 424 assert.Empty(newMB.Spec.PrometheusConfigMap.Name) 425 assert.Equal(vzconst.PrometheusOperatorNamespace, newMB.Spec.PrometheusConfigSecret.Namespace) 426 assert.Equal(vzconst.PromAdditionalScrapeConfigsSecretName, newMB.Spec.PrometheusConfigSecret.Name) 427 assert.Equal(vzconst.PromAdditionalScrapeConfigsSecretKey, newMB.Spec.PrometheusConfigSecret.Key) 428 429 updatedSecret := corev1.Secret{} 430 err = client.Get(context.TODO(), types.NamespacedName{Namespace: vzconst.PrometheusOperatorNamespace, Name: vzconst.PromAdditionalScrapeConfigsSecretName}, &updatedSecret) 431 assert.NoError(err) 432 scrapeConfigs := updatedSecret.Data[vzconst.PromAdditionalScrapeConfigsSecretKey] 433 assert.NotNil(scrapeConfigs, "Expected additional scrape config secret to contain the scrape config") 434 assert.Contains(string(scrapeConfigs), tt.workload.Name) 435 } 436 newMB := vzapi.MetricsBinding{} 437 err = client.Get(context.TODO(), types.NamespacedName{Namespace: tt.metricsBinding.Namespace, Name: tt.metricsBinding.Name}, &newMB) 438 assert.NoError(err) 439 assert.Equal(1, len(newMB.Finalizers)) 440 assert.Equal(finalizerName, newMB.Finalizers[0]) 441 assert.Equal(1, len(newMB.OwnerReferences)) 442 assert.Equal(tt.workload.Name, newMB.OwnerReferences[0].Name) 443 }) 444 } 445 }