github.com/verrazzano/verrazzano@v1.7.0/application-operator/controllers/webhooks/metrics-binding-updater-workload_test.go (about) 1 // Copyright (c) 2021, 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 webhooks 5 6 import ( 7 "context" 8 "encoding/json" 9 "testing" 10 11 "github.com/stretchr/testify/assert" 12 vzapp "github.com/verrazzano/verrazzano/application-operator/apis/app/v1alpha1" 13 "github.com/verrazzano/verrazzano/application-operator/constants" 14 vzconst "github.com/verrazzano/verrazzano/pkg/constants" 15 admissionv1 "k8s.io/api/admission/v1" 16 appsv1 "k8s.io/api/apps/v1" 17 corev1 "k8s.io/api/core/v1" 18 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 19 "k8s.io/apimachinery/pkg/runtime" 20 "k8s.io/apimachinery/pkg/runtime/schema" 21 "k8s.io/apimachinery/pkg/types" 22 "k8s.io/client-go/kubernetes/fake" 23 ctrlfake "sigs.k8s.io/controller-runtime/pkg/client/fake" 24 "sigs.k8s.io/controller-runtime/pkg/webhook/admission" 25 ) 26 27 const testNS = "test" 28 const testConfigMapName = "testPromConfigMap" 29 const testTemplateWorkloadNamespace = "testTemplateWorkloadNamespace" 30 const appsv1APIVersion = "apps/v1" 31 const testDeploymentName = "testDeployment" 32 33 // newGeneratorWorkloadWebhook creates a new WorkloadWebhook 34 func newGeneratorWorkloadWebhook() WorkloadWebhook { 35 scheme := newScheme() 36 scheme.AddKnownTypes(schema.GroupVersion{ 37 Version: "v1", 38 }, &corev1.Pod{}, &appsv1.Deployment{}, &appsv1.ReplicaSet{}, &appsv1.StatefulSet{}, &corev1.Namespace{}) 39 _ = vzapp.AddToScheme(scheme) 40 decoder, _ := admission.NewDecoder(scheme) 41 cli := ctrlfake.NewClientBuilder().WithScheme(scheme).Build() 42 v := WorkloadWebhook{ 43 Client: cli, 44 Decoder: decoder, 45 KubeClient: fake.NewSimpleClientset(), 46 } 47 return v 48 } 49 50 // newGeneratorWorkloadRequest creates a new admissionRequest with the provided operation and object. 51 func newGeneratorWorkloadRequest(op admissionv1.Operation, kind string, obj interface{}) admission.Request { 52 raw := runtime.RawExtension{} 53 bytes, _ := json.Marshal(obj) 54 raw.Raw = bytes 55 req := admission.Request{ 56 AdmissionRequest: admissionv1.AdmissionRequest{ 57 Kind: metav1.GroupVersionKind{ 58 Kind: kind, 59 }, 60 Operation: op, Object: raw}} 61 return req 62 } 63 64 // TestHandlePod tests the handling of a Pod resource 65 // GIVEN a call validate Pod on create or update 66 // WHEN the Pod is properly formed 67 // THEN the validation should succeed 68 func TestHandlePod(t *testing.T) { 69 70 v := newGeneratorWorkloadWebhook() 71 72 // Test data 73 v.createNamespace(t, "test", nil, nil) 74 testPod := corev1.Pod{ 75 ObjectMeta: metav1.ObjectMeta{ 76 Name: "test", 77 Namespace: "test", 78 }, 79 } 80 assert.NoError(t, v.Client.Create(context.TODO(), &testPod)) 81 82 req := newGeneratorWorkloadRequest(admissionv1.Create, "Pod", testPod) 83 res := v.Handle(context.TODO(), req) 84 assert.True(t, res.Allowed, "Expected validation to succeed.") 85 } 86 87 // TestHandleDeployment tests the handling of a Deployment resource 88 // GIVEN a call validate Deployment on create or update 89 // WHEN the Deployment is properly formed 90 // THEN the validation should succeed 91 func TestHandleDeployment(t *testing.T) { 92 93 v := newGeneratorWorkloadWebhook() 94 95 // Test data 96 v.createNamespace(t, "test", nil, nil) 97 testDeployment := appsv1.Deployment{ 98 ObjectMeta: metav1.ObjectMeta{ 99 Name: testDeploymentName, 100 Namespace: "test", 101 }, 102 } 103 assert.NoError(t, v.Client.Create(context.TODO(), &testDeployment)) 104 105 req := newGeneratorWorkloadRequest(admissionv1.Create, "Deployment", testDeployment) 106 res := v.Handle(context.TODO(), req) 107 assert.True(t, res.Allowed, "Expected validation to succeed.") 108 } 109 110 // TestHandleReplicaSet tests the handling of a ReplicaSet resource 111 // GIVEN a call validate ReplicaSet on create or update 112 // WHEN the ReplicaSet is properly formed 113 // THEN the validation should succeed 114 func TestHandleReplicaSet(t *testing.T) { 115 116 v := newGeneratorWorkloadWebhook() 117 118 // Test data 119 v.createNamespace(t, "test", nil, nil) 120 testReplicaSet := appsv1.ReplicaSet{ 121 ObjectMeta: metav1.ObjectMeta{ 122 Name: "testReplicaSet", 123 Namespace: "test", 124 }, 125 } 126 assert.NoError(t, v.Client.Create(context.TODO(), &testReplicaSet)) 127 128 req := newGeneratorWorkloadRequest(admissionv1.Create, "ReplicaSet", testReplicaSet) 129 res := v.Handle(context.TODO(), req) 130 assert.True(t, res.Allowed, "Expected validation to succeed.") 131 } 132 133 // TestHandleStatefulSet tests the handling of a StatefulSet resource 134 // GIVEN a call validate StatefulSet on create or update 135 // WHEN the StatefulSet is properly formed 136 // THEN the validation should succeed 137 func TestHandleStatefulSet(t *testing.T) { 138 139 v := newGeneratorWorkloadWebhook() 140 141 // Test data 142 v.createNamespace(t, "test", nil, nil) 143 testStatefulSet := appsv1.StatefulSet{ 144 ObjectMeta: metav1.ObjectMeta{ 145 Name: "testStatefulSet", 146 Namespace: "test", 147 }, 148 } 149 assert.NoError(t, v.Client.Create(context.TODO(), &testStatefulSet)) 150 151 req := newGeneratorWorkloadRequest(admissionv1.Create, "StatefulSet", testStatefulSet) 152 res := v.Handle(context.TODO(), req) 153 assert.True(t, res.Allowed, "Expected validation to succeed.") 154 } 155 156 // TestHandleOwnerRefs tests the handling of a workload resource with owner references 157 // GIVEN a call to the webhook Handle function 158 // WHEN the workload resource has owner references 159 // THEN the Handle function should succeed and no metricsBinding is not created 160 func TestHandleOwnerRefs(t *testing.T) { 161 162 v := newGeneratorWorkloadWebhook() 163 164 // Test data 165 v.createNamespace(t, "test", nil, nil) 166 testDeployment := appsv1.Deployment{ 167 ObjectMeta: metav1.ObjectMeta{ 168 Name: testDeploymentName, 169 Namespace: "test", 170 OwnerReferences: []metav1.OwnerReference{ 171 { 172 Name: "foo", 173 }, 174 }, 175 }, 176 } 177 assert.NoError(t, v.Client.Create(context.TODO(), &testDeployment)) 178 179 req := newGeneratorWorkloadRequest(admissionv1.Create, "Deployment", testDeployment) 180 res := v.Handle(context.TODO(), req) 181 assert.True(t, res.Allowed) 182 assert.Nil(t, res.Patches, "expected no changes to workload resource") 183 184 // validate that metrics binding was not created as expected 185 v.validateNoMetricsBinding(t) 186 } 187 188 // TestHandleMetricsNone tests the handling of a workload resource with "app.verrazzano.io/metrics": "none" 189 // GIVEN a call to the webhook Handle function 190 // WHEN the workload resource has "app.verrazzano.io/metrics": "none" 191 // THEN the Handle function should succeed and the metricsBinding is not created 192 func TestHandleMetricsNone(t *testing.T) { 193 194 v := newGeneratorWorkloadWebhook() 195 196 // Test data 197 v.createNamespace(t, "test", map[string]string{vzconst.VerrazzanoManagedLabelKey: "true"}, nil) 198 testDeployment := appsv1.Deployment{ 199 ObjectMeta: metav1.ObjectMeta{ 200 Name: testDeploymentName, 201 Namespace: "test", 202 Annotations: map[string]string{ 203 MetricsAnnotation: "none", 204 }, 205 }, 206 } 207 assert.NoError(t, v.Client.Create(context.TODO(), &testDeployment)) 208 209 req := newGeneratorWorkloadRequest(admissionv1.Create, "Deployment", testDeployment) 210 res := v.Handle(context.TODO(), req) 211 assert.True(t, res.Allowed) 212 assert.Nil(t, res.Patches, "expected no changes to workload resource") 213 214 // validate that metrics binding was not created as expected 215 v.validateNoMetricsBinding(t) 216 } 217 218 // TestHandleMetricsNoneNamespace tests the handling of a namespace with "app.verrazzano.io/metrics": "none" 219 // GIVEN a call to the webhook Handle function 220 // WHEN the workload resource has "app.verrazzano.io/metrics": "none" 221 // THEN the Handle function should succeed and the metricsBinding is not created 222 func TestHandleMetricsNoneNamespace(t *testing.T) { 223 224 v := newGeneratorWorkloadWebhook() 225 226 // Test data 227 v.createNamespace(t, "test", map[string]string{vzconst.VerrazzanoManagedLabelKey: "true"}, map[string]string{MetricsAnnotation: "none"}) 228 testDeployment := appsv1.Deployment{ 229 ObjectMeta: metav1.ObjectMeta{ 230 Name: testDeploymentName, 231 Namespace: "test", 232 }, 233 } 234 assert.NoError(t, v.Client.Create(context.TODO(), &testDeployment)) 235 236 req := newGeneratorWorkloadRequest(admissionv1.Create, "Deployment", testDeployment) 237 res := v.Handle(context.TODO(), req) 238 assert.True(t, res.Allowed) 239 assert.Nil(t, res.Patches, "expected no changes to workload resource") 240 241 // validate that metrics binding was not created as expected 242 v.validateNoMetricsBinding(t) 243 } 244 245 // TestHandleInvalidMetricsTemplate tests the handling of a workload resource with references a metrics template 246 // that does not exist 247 // GIVEN a call to the webhook Handle function 248 // WHEN the workload resource has "app.verrazzano.io/metrics": "badTemplate" 249 // THEN the Handle function should 250 // Generate an error for a pre-VZ 1.4 app with existing metrics binding 251 // Allow the workload for an app with no existing metrics binding 252 func TestHandleInvalidMetricsTemplate(t *testing.T) { 253 254 tests := []struct { 255 name string 256 metricsBindingExists bool 257 expectAllowed bool 258 expectedError string 259 }{ 260 {"legacy app with existing MetricsBinding", true, false, "metricstemplates.app.verrazzano.io \"badTemplate\" not found"}, 261 {"new app with no MetricsBinding present", false, true, ""}, 262 } 263 for _, tt := range tests { 264 t.Run(tt.name, func(t *testing.T) { 265 v := newGeneratorWorkloadWebhook() 266 267 // Test data 268 v.createNamespace(t, "test", map[string]string{vzconst.VerrazzanoManagedLabelKey: "true"}, nil) 269 testDeployment := appsv1.Deployment{ 270 TypeMeta: metav1.TypeMeta{ 271 Kind: "Deployment", 272 APIVersion: appsv1APIVersion, 273 }, 274 ObjectMeta: metav1.ObjectMeta{ 275 Name: testDeploymentName, 276 Namespace: "test", 277 Annotations: map[string]string{ 278 MetricsAnnotation: "badTemplate", 279 }, 280 }, 281 } 282 assert.NoError(t, v.Client.Create(context.TODO(), &testDeployment)) 283 284 if tt.metricsBindingExists { 285 existingMetricsBinding := vzapp.MetricsBinding{ 286 ObjectMeta: metav1.ObjectMeta{ 287 Namespace: "test", 288 Name: generateMetricsBindingName(testDeployment.Name, testDeployment.APIVersion, testDeployment.Kind), 289 }, 290 } 291 assert.NoError(t, v.Client.Create(context.TODO(), &existingMetricsBinding)) 292 } 293 294 req := newGeneratorWorkloadRequest(admissionv1.Create, vzconst.DeploymentWorkloadKind, testDeployment) 295 res := v.Handle(context.TODO(), req) 296 assert.Equal(t, tt.expectAllowed, res.Allowed) 297 if !tt.expectAllowed { 298 assert.Equal(t, tt.expectedError, res.Result.Message) 299 } 300 }) 301 } 302 } 303 304 // TestHandleMetricsTemplateWorkloadNamespace tests the handling of a workload resource which references a metrics 305 // template found in the namespace of the workload resource that already has a MetricsBinding 306 // GIVEN a call to the webhook Handle function 307 // WHEN the workload resource has a valid metrics template reference 308 // THEN the Handle function should succeed and the existing MetricsBinding is updated 309 func TestHandleMetricsTemplateWorkloadNamespace(t *testing.T) { 310 311 tests := []struct { 312 name string 313 metricsBindingExists bool 314 expectAllowed bool 315 expectMetricsBindingUpdate bool 316 expectServiceMonitor bool 317 }{ 318 {"legacy app with existing MetricsBinding", true, true, true, false}, 319 {"new app with no MetricsBinding present", false, true, false, false}, 320 } 321 for _, tt := range tests { 322 t.Run(tt.name, func(t *testing.T) { 323 v := newGeneratorWorkloadWebhook() 324 325 // Test data 326 v.createNamespace(t, "test", map[string]string{vzconst.VerrazzanoManagedLabelKey: "true"}, nil) 327 v.createConfigMap(t, "test", testConfigMapName) 328 testDeployment := appsv1.Deployment{ 329 TypeMeta: metav1.TypeMeta{ 330 Kind: vzconst.DeploymentWorkloadKind, 331 APIVersion: appsv1APIVersion, 332 }, 333 ObjectMeta: metav1.ObjectMeta{ 334 Name: testDeploymentName, 335 Namespace: "test", 336 UID: "11", 337 Annotations: map[string]string{ 338 MetricsAnnotation: testTemplateWorkloadNamespace, 339 }, 340 }, 341 } 342 assert.NoError(t, v.Client.Create(context.TODO(), &testDeployment)) 343 344 if tt.metricsBindingExists { 345 existingMetricsBinding := vzapp.MetricsBinding{ 346 ObjectMeta: metav1.ObjectMeta{ 347 Name: generateMetricsBindingName(testDeployment.Name, testDeployment.APIVersion, testDeployment.Kind), 348 Namespace: "test", 349 }, 350 } 351 assert.NoError(t, v.Client.Create(context.TODO(), &existingMetricsBinding)) 352 } 353 354 testTemplate := vzapp.MetricsTemplate{ 355 ObjectMeta: metav1.ObjectMeta{ 356 Namespace: "test", 357 Name: testTemplateWorkloadNamespace, 358 }, 359 Spec: vzapp.MetricsTemplateSpec{ 360 WorkloadSelector: vzapp.WorkloadSelector{}, 361 PrometheusConfig: vzapp.PrometheusConfig{ 362 TargetConfigMap: vzapp.TargetConfigMap{ 363 Namespace: "test", 364 Name: testConfigMapName, 365 }, 366 }, 367 }, 368 } 369 assert.NoError(t, v.Client.Create(context.TODO(), &testTemplate)) 370 371 req := newGeneratorWorkloadRequest(admissionv1.Create, vzconst.DeploymentWorkloadKind, testDeployment) 372 res := v.Handle(context.TODO(), req) 373 374 assert.Equal(t, tt.expectAllowed, res.Allowed) 375 376 if tt.expectMetricsBindingUpdate { 377 assert.Len(t, res.Patches, 1) 378 assert.Equal(t, "add", res.Patches[0].Operation) 379 assert.Equal(t, "/metadata/labels", res.Patches[0].Path) 380 assert.Contains(t, res.Patches[0].Value, constants.MetricsWorkloadLabel) 381 382 // validate that metrics binding was updated as expected 383 v.validateMetricsBinding(t, testNS, testTemplateWorkloadNamespace, testNS, testConfigMapName, false) 384 } else { 385 assert.Len(t, res.Patches, 0) 386 v.validateNoMetricsBinding(t) 387 } 388 }) 389 } 390 } 391 392 // TestHandleMetricsTemplateSystemNamespace tests the handling of a workload resource which references a metrics 393 // template found in the verrazzano-system namespace 394 // GIVEN a call to the webhook Handle function 395 // WHEN the workload resource has a valid metrics template reference 396 // THEN the Handle function should succeed 397 // AND for a pre-VZ 1.4 app with existing MetricsBinding, the metricsBinding is updated 398 // BUT for an app with no existing MetricsBinding, no action is taken 399 func TestHandleMetricsTemplateSystemNamespace(t *testing.T) { 400 401 tests := []struct { 402 name string 403 metricsBindingExists bool 404 expectAllowed bool 405 expectMetricsBindingUpdate bool 406 expectServiceMonitor bool 407 }{ 408 {"legacy app with existing MetricsBinding", true, true, true, false}, 409 {"new app with no MetricsBinding present", false, true, false, false}, 410 } 411 for _, tt := range tests { 412 t.Run(tt.name, func(t *testing.T) { 413 v := newGeneratorWorkloadWebhook() 414 // Test data 415 v.createNamespace(t, testNS, map[string]string{vzconst.VerrazzanoManagedLabelKey: "true"}, nil) 416 v.createNamespace(t, vzconst.VerrazzanoSystemNamespace, nil, nil) 417 v.createConfigMap(t, testNS, testConfigMapName) 418 testDeployment := appsv1.Deployment{ 419 TypeMeta: metav1.TypeMeta{ 420 Kind: vzconst.DeploymentWorkloadKind, 421 APIVersion: appsv1APIVersion, 422 }, 423 ObjectMeta: metav1.ObjectMeta{ 424 Name: testDeploymentName, 425 Namespace: testNS, 426 UID: "11", 427 Annotations: map[string]string{ 428 MetricsAnnotation: "testTemplateSameNamespace", 429 }, 430 }, 431 } 432 assert.NoError(t, v.Client.Create(context.TODO(), &testDeployment)) 433 434 if tt.metricsBindingExists { 435 existingMetricsBinding := vzapp.MetricsBinding{ 436 ObjectMeta: metav1.ObjectMeta{ 437 Name: generateMetricsBindingName(testDeployment.Name, testDeployment.APIVersion, testDeployment.Kind), 438 Namespace: testNS, 439 }, 440 } 441 assert.NoError(t, v.Client.Create(context.TODO(), &existingMetricsBinding)) 442 } 443 444 testTemplate := vzapp.MetricsTemplate{ 445 ObjectMeta: metav1.ObjectMeta{ 446 Namespace: vzconst.VerrazzanoSystemNamespace, 447 Name: "testTemplateSameNamespace", 448 }, 449 Spec: vzapp.MetricsTemplateSpec{ 450 WorkloadSelector: vzapp.WorkloadSelector{}, 451 PrometheusConfig: vzapp.PrometheusConfig{ 452 TargetConfigMap: vzapp.TargetConfigMap{ 453 Namespace: testNS, 454 Name: testConfigMapName, 455 }, 456 }, 457 }, 458 } 459 assert.NoError(t, v.Client.Create(context.TODO(), &testTemplate)) 460 461 req := newGeneratorWorkloadRequest(admissionv1.Create, vzconst.DeploymentWorkloadKind, testDeployment) 462 res := v.Handle(context.TODO(), req) 463 assert.Equal(t, tt.expectAllowed, res.Allowed) 464 if tt.expectMetricsBindingUpdate { 465 assert.Len(t, res.Patches, 1) 466 assert.Equal(t, "add", res.Patches[0].Operation) 467 assert.Equal(t, "/metadata/labels", res.Patches[0].Path) 468 assert.Contains(t, res.Patches[0].Value, constants.MetricsWorkloadLabel) 469 470 // validate that metrics binding was updated as expected 471 v.validateMetricsBinding(t, vzconst.VerrazzanoSystemNamespace, "testTemplateSameNamespace", testNS, testConfigMapName, false) 472 } else { 473 assert.Len(t, res.Patches, 0) 474 v.validateNoMetricsBinding(t) 475 } 476 }) 477 } 478 } 479 480 // TestHandleMetricsTemplateConfigMapNotFound tests the handling of a workload resource which references a metrics 481 // template found with Prometheus config map that does not exist 482 // GIVEN a call to the webhook Handle function 483 // WHEN the workload resource has an invalid Prometheus config map reference 484 // THEN the Handle function should 485 // fail and return an error for a pre-VZ 1.4 app with existing metrics binding 486 // Allow the workload for an app with no existing metrics binding 487 488 func TestHandleMetricsTemplateConfigMapNotFound(t *testing.T) { 489 490 tests := []struct { 491 name string 492 metricsBindingExists bool 493 expectAllowed bool 494 expectMetricsBindingError bool 495 }{ 496 {"legacy app with existing MetricsBinding", true, false, true}, 497 {"new app with no MetricsBinding present", false, true, false}, 498 } 499 for _, tt := range tests { 500 t.Run(tt.name, func(t *testing.T) { 501 502 v := newGeneratorWorkloadWebhook() 503 504 // Test data 505 v.createNamespace(t, testNS, map[string]string{vzconst.VerrazzanoManagedLabelKey: "true"}, nil) 506 testDeployment := appsv1.Deployment{ 507 ObjectMeta: metav1.ObjectMeta{ 508 Name: "test", 509 Namespace: testNS, 510 UID: "11", 511 Annotations: map[string]string{ 512 MetricsAnnotation: testTemplateWorkloadNamespace, 513 }, 514 }, 515 TypeMeta: metav1.TypeMeta{ 516 Kind: vzconst.DeploymentWorkloadKind, 517 APIVersion: appsv1APIVersion, 518 }, 519 } 520 assert.NoError(t, v.Client.Create(context.TODO(), &testDeployment)) 521 522 if tt.metricsBindingExists { 523 existingMetricsBinding := vzapp.MetricsBinding{ 524 ObjectMeta: metav1.ObjectMeta{ 525 Name: generateMetricsBindingName(testDeployment.Name, testDeployment.APIVersion, testDeployment.Kind), 526 Namespace: testNS, 527 }, 528 } 529 assert.NoError(t, v.Client.Create(context.TODO(), &existingMetricsBinding)) 530 } 531 532 testTemplate := vzapp.MetricsTemplate{ 533 ObjectMeta: metav1.ObjectMeta{ 534 Namespace: testNS, 535 Name: testTemplateWorkloadNamespace, 536 }, 537 Spec: vzapp.MetricsTemplateSpec{ 538 WorkloadSelector: vzapp.WorkloadSelector{}, 539 PrometheusConfig: vzapp.PrometheusConfig{ 540 TargetConfigMap: vzapp.TargetConfigMap{ 541 Namespace: testNS, 542 Name: testConfigMapName, 543 }, 544 }, 545 }, 546 } 547 assert.NoError(t, v.Client.Create(context.TODO(), &testTemplate)) 548 549 req := newGeneratorWorkloadRequest(admissionv1.Create, vzconst.DeploymentWorkloadKind, testDeployment) 550 res := v.Handle(context.TODO(), req) 551 assert.Equal(t, tt.expectAllowed, res.Allowed) 552 if tt.expectMetricsBindingError { 553 assert.Equal(t, "configmaps \"testPromConfigMap\" not found", res.Result.Message) 554 } 555 }) 556 } 557 } 558 559 // TestHandleMatchWorkloadNamespace tests the handling of a workload resource with no metrics template specified 560 // but matches a template found in the workload resources namespace 561 // GIVEN a call to the webhook Handle function 562 // WHEN the workload resource has no metrics template reference 563 // THEN the Handle function should succeed 564 // AND for a pre-VZ 1.4 app, the existing legacy MetricsBinding is updated 565 // BUT for an app with no existing MetricsBinding, no action is taken. It's up to user to create a monitor resource. 566 func TestHandleMatchWorkloadNamespace(t *testing.T) { 567 568 tests := []struct { 569 name string 570 metricsBindingExists bool 571 expectAllowed bool 572 expectMetricsBindingUpdate bool 573 }{ 574 {"legacy app with existing MetricsBinding", true, true, true}, 575 {"new app with no MetricsBinding present", false, true, false}, 576 } 577 for _, tt := range tests { 578 t.Run(tt.name, func(t *testing.T) { 579 v := newGeneratorWorkloadWebhook() 580 // Test data 581 v.createNamespace(t, testNS, map[string]string{vzconst.VerrazzanoManagedLabelKey: "true"}, nil) 582 v.createNamespace(t, vzconst.VerrazzanoSystemNamespace, nil, nil) 583 v.createConfigMap(t, testNS, testConfigMapName) 584 testDeployment := appsv1.Deployment{ 585 TypeMeta: metav1.TypeMeta{ 586 Kind: vzconst.DeploymentWorkloadKind, 587 APIVersion: appsv1APIVersion, 588 }, 589 ObjectMeta: metav1.ObjectMeta{ 590 Name: testDeploymentName, 591 Namespace: testNS, 592 UID: "11", 593 }, 594 } 595 assert.NoError(t, v.Client.Create(context.TODO(), &testDeployment)) 596 597 if tt.metricsBindingExists { 598 existingMetricsBinding := vzapp.MetricsBinding{ 599 ObjectMeta: metav1.ObjectMeta{ 600 Name: generateMetricsBindingName(testDeployment.Name, testDeployment.APIVersion, testDeployment.Kind), 601 Namespace: testNS, 602 }, 603 } 604 assert.NoError(t, v.Client.Create(context.TODO(), &existingMetricsBinding)) 605 } 606 607 testTemplate := vzapp.MetricsTemplate{ 608 ObjectMeta: metav1.ObjectMeta{ 609 Namespace: testNS, 610 Name: testTemplateWorkloadNamespace, 611 }, 612 Spec: vzapp.MetricsTemplateSpec{ 613 WorkloadSelector: vzapp.WorkloadSelector{ 614 APIGroups: []string{ 615 "apps", 616 }, 617 APIVersions: []string{ 618 "v1", 619 }, 620 Resources: []string{ 621 "deployment", 622 }, 623 }, 624 PrometheusConfig: vzapp.PrometheusConfig{ 625 TargetConfigMap: vzapp.TargetConfigMap{ 626 Namespace: testNS, 627 Name: testConfigMapName, 628 }, 629 }, 630 }, 631 } 632 assert.NoError(t, v.Client.Create(context.TODO(), &testTemplate)) 633 634 req := newGeneratorWorkloadRequest(admissionv1.Create, vzconst.DeploymentWorkloadKind, testDeployment) 635 res := v.Handle(context.TODO(), req) 636 assert.Equal(t, tt.expectAllowed, res.Allowed) 637 if tt.expectMetricsBindingUpdate { 638 assert.Len(t, res.Patches, 1) 639 assert.Equal(t, "add", res.Patches[0].Operation) 640 assert.Equal(t, "/metadata/labels", res.Patches[0].Path) 641 assert.Contains(t, res.Patches[0].Value, constants.MetricsWorkloadLabel) 642 // validate that metrics binding was updated as expected 643 v.validateMetricsBinding(t, testNS, testTemplateWorkloadNamespace, testNS, testConfigMapName, false) 644 } else { 645 assert.Len(t, res.Patches, 0) 646 v.validateNoMetricsBinding(t) 647 } 648 }) 649 } 650 651 } 652 653 // TestHandleMatchSystemNamespace tests the handling of a workload resource with no metrics template specified 654 // but matches a template found in the verrazzano-system namespace 655 // GIVEN a call to the webhook Handle function 656 // WHEN the workload resource has no metrics template reference 657 // THEN the Handle function should succeed 658 // AND for a pre-VZ 1.4 app, the existing legacy MetricsBinding is updated 659 // BUT for an app with no existing MetricsBinding, no action is taken. It's up to user to create a monitor resource. 660 func TestHandleMatchSystemNamespace(t *testing.T) { 661 662 tests := []struct { 663 name string 664 metricsBindingExists bool 665 expectAllowed bool 666 expectMetricsBindingUpdate bool 667 }{ 668 {"legacy app with existing MetricsBinding", true, true, true}, 669 {"new app with no MetricsBinding present", false, true, false}, 670 } 671 for _, tt := range tests { 672 t.Run(tt.name, func(t *testing.T) { 673 674 v := newGeneratorWorkloadWebhook() 675 676 // Test data 677 v.createNamespace(t, testNS, map[string]string{vzconst.VerrazzanoManagedLabelKey: "true"}, nil) 678 v.createNamespace(t, vzconst.VerrazzanoSystemNamespace, nil, nil) 679 v.createConfigMap(t, testNS, testConfigMapName) 680 testDeployment := appsv1.Deployment{ 681 TypeMeta: metav1.TypeMeta{ 682 Kind: vzconst.DeploymentWorkloadKind, 683 APIVersion: appsv1APIVersion, 684 }, 685 ObjectMeta: metav1.ObjectMeta{ 686 Name: testDeploymentName, 687 Namespace: testNS, 688 UID: "11", 689 }, 690 } 691 assert.NoError(t, v.Client.Create(context.TODO(), &testDeployment)) 692 693 if tt.metricsBindingExists { 694 existingMetricsBinding := vzapp.MetricsBinding{ 695 ObjectMeta: metav1.ObjectMeta{ 696 Name: generateMetricsBindingName(testDeployment.Name, testDeployment.APIVersion, testDeployment.Kind), 697 Namespace: testNS, 698 }, 699 } 700 assert.NoError(t, v.Client.Create(context.TODO(), &existingMetricsBinding)) 701 } 702 703 testTemplate := vzapp.MetricsTemplate{ 704 ObjectMeta: metav1.ObjectMeta{ 705 Namespace: vzconst.VerrazzanoSystemNamespace, 706 Name: "testTemplateSystemNamespace", 707 }, 708 Spec: vzapp.MetricsTemplateSpec{ 709 WorkloadSelector: vzapp.WorkloadSelector{ 710 APIGroups: []string{ 711 "apps", 712 }, 713 APIVersions: []string{ 714 "v1", 715 }, 716 Resources: []string{ 717 "deployment", 718 }, 719 }, 720 PrometheusConfig: vzapp.PrometheusConfig{ 721 TargetConfigMap: vzapp.TargetConfigMap{ 722 Namespace: testNS, 723 Name: testConfigMapName, 724 }, 725 }, 726 }, 727 } 728 assert.NoError(t, v.Client.Create(context.TODO(), &testTemplate)) 729 730 req := newGeneratorWorkloadRequest(admissionv1.Create, vzconst.DeploymentWorkloadKind, testDeployment) 731 res := v.Handle(context.TODO(), req) 732 assert.Equal(t, tt.expectAllowed, res.Allowed) 733 if tt.expectMetricsBindingUpdate { 734 assert.Len(t, res.Patches, 1) 735 assert.Equal(t, "add", res.Patches[0].Operation) 736 assert.Equal(t, "/metadata/labels", res.Patches[0].Path) 737 assert.Contains(t, res.Patches[0].Value, constants.MetricsWorkloadLabel) 738 739 // validate that metrics binding was updated as expected 740 v.validateMetricsBinding(t, vzconst.VerrazzanoSystemNamespace, "testTemplateSystemNamespace", testNS, testConfigMapName, false) 741 } else { 742 assert.Len(t, res.Patches, 0) 743 v.validateNoMetricsBinding(t) 744 } 745 }) 746 } 747 } 748 749 // TestHandleMatchNotFound tests the handling of a workload resource with no metrics template specified 750 // and a matching template not found 751 // GIVEN a call to the webhook Handle function 752 // WHEN the workload resource has no metrics template reference 753 // THEN the Handle function should succeed and no metricsBinding is created 754 func TestHandleMatchNotFound(t *testing.T) { 755 756 v := newGeneratorWorkloadWebhook() 757 758 // Test data 759 v.createNamespace(t, testNS, map[string]string{vzconst.VerrazzanoManagedLabelKey: "true"}, nil) 760 v.createNamespace(t, vzconst.VerrazzanoSystemNamespace, nil, nil) 761 v.createConfigMap(t, testNS, testConfigMapName) 762 testDeployment := appsv1.Deployment{ 763 TypeMeta: metav1.TypeMeta{ 764 Kind: vzconst.DeploymentWorkloadKind, 765 APIVersion: appsv1APIVersion, 766 }, 767 ObjectMeta: metav1.ObjectMeta{ 768 Name: testDeploymentName, 769 Namespace: testNS, 770 UID: "11", 771 }, 772 } 773 assert.NoError(t, v.Client.Create(context.TODO(), &testDeployment)) 774 testTemplate := vzapp.MetricsTemplate{ 775 ObjectMeta: metav1.ObjectMeta{ 776 Namespace: vzconst.VerrazzanoSystemNamespace, 777 Name: "testTemplateSystemNamespace", 778 }, 779 Spec: vzapp.MetricsTemplateSpec{ 780 WorkloadSelector: vzapp.WorkloadSelector{ 781 APIGroups: []string{ 782 "apps", 783 }, 784 APIVersions: []string{ 785 "*", 786 }, 787 Resources: []string{ 788 "foo", 789 }, 790 }, 791 PrometheusConfig: vzapp.PrometheusConfig{ 792 TargetConfigMap: vzapp.TargetConfigMap{ 793 Namespace: testNS, 794 Name: testConfigMapName, 795 }, 796 }, 797 }, 798 } 799 assert.NoError(t, v.Client.Create(context.TODO(), &testTemplate)) 800 801 req := newGeneratorWorkloadRequest(admissionv1.Create, vzconst.DeploymentWorkloadKind, testDeployment) 802 res := v.Handle(context.TODO(), req) 803 assert.True(t, res.Allowed) 804 assert.Empty(t, res.Patches) 805 806 // validate that metrics binding was not created as expected 807 v.validateNoMetricsBinding(t) 808 } 809 810 // TestHandleMatchTemplateNoWorkloadSelector tests the handling of a workload resource with no metrics template specified 811 // and a metrics template that doesn't have a workload selector specified 812 // GIVEN a call to the webhook Handle function 813 // WHEN the workload resource has no metrics template reference 814 // THEN the Handle function should succeed and no metricsBinding is created 815 func TestHandleMatchTemplateNoWorkloadSelector(t *testing.T) { 816 817 v := newGeneratorWorkloadWebhook() 818 819 // Test data 820 v.createNamespace(t, testNS, map[string]string{vzconst.VerrazzanoManagedLabelKey: "true"}, nil) 821 v.createNamespace(t, vzconst.VerrazzanoSystemNamespace, nil, nil) 822 v.createConfigMap(t, testNS, testConfigMapName) 823 testDeployment := appsv1.Deployment{ 824 TypeMeta: metav1.TypeMeta{ 825 Kind: vzconst.DeploymentWorkloadKind, 826 APIVersion: appsv1APIVersion, 827 }, 828 ObjectMeta: metav1.ObjectMeta{ 829 Name: testDeploymentName, 830 Namespace: testNS, 831 UID: "11", 832 }, 833 } 834 assert.NoError(t, v.Client.Create(context.TODO(), &testDeployment)) 835 testTemplate := vzapp.MetricsTemplate{ 836 ObjectMeta: metav1.ObjectMeta{ 837 Namespace: vzconst.VerrazzanoSystemNamespace, 838 Name: "testTemplateSystemNamespace", 839 }, 840 Spec: vzapp.MetricsTemplateSpec{ 841 PrometheusConfig: vzapp.PrometheusConfig{ 842 TargetConfigMap: vzapp.TargetConfigMap{ 843 Namespace: testNS, 844 Name: testConfigMapName, 845 }, 846 }, 847 }, 848 } 849 assert.NoError(t, v.Client.Create(context.TODO(), &testTemplate)) 850 851 req := newGeneratorWorkloadRequest(admissionv1.Create, vzconst.DeploymentWorkloadKind, testDeployment) 852 res := v.Handle(context.TODO(), req) 853 assert.True(t, res.Allowed) 854 assert.Empty(t, res.Patches) 855 856 // validate that metrics binding was not created as expected 857 v.validateNoMetricsBinding(t) 858 } 859 860 // TestHandleNoConfigMap tests the handling of a workload resource that doesn't have a Prometheus target 861 // config map specified in the metrics template 862 // GIVEN a call to the webhook Handle function 863 // WHEN the workload resource has a metrics template reference 864 // THEN the Handle function should succeed and no metricsBinding is created 865 func TestHandleNoConfigMap(t *testing.T) { 866 867 v := newGeneratorWorkloadWebhook() 868 869 // Test data 870 v.createNamespace(t, testNS, map[string]string{vzconst.VerrazzanoManagedLabelKey: "true"}, nil) 871 v.createNamespace(t, vzconst.VerrazzanoSystemNamespace, nil, nil) 872 testDeployment := appsv1.Deployment{ 873 TypeMeta: metav1.TypeMeta{ 874 Kind: vzconst.DeploymentWorkloadKind, 875 APIVersion: appsv1APIVersion, 876 }, 877 ObjectMeta: metav1.ObjectMeta{ 878 Name: testDeploymentName, 879 Namespace: testNS, 880 Annotations: map[string]string{ 881 MetricsAnnotation: testTemplateWorkloadNamespace, 882 }, 883 UID: "11", 884 }, 885 } 886 assert.NoError(t, v.Client.Create(context.TODO(), &testDeployment)) 887 testTemplate := vzapp.MetricsTemplate{ 888 ObjectMeta: metav1.ObjectMeta{ 889 Namespace: testNS, 890 Name: testTemplateWorkloadNamespace, 891 }, 892 Spec: vzapp.MetricsTemplateSpec{}, 893 } 894 assert.NoError(t, v.Client.Create(context.TODO(), &testTemplate)) 895 896 req := newGeneratorWorkloadRequest(admissionv1.Create, vzconst.DeploymentWorkloadKind, testDeployment) 897 res := v.Handle(context.TODO(), req) 898 assert.True(t, res.Allowed) 899 assert.Empty(t, res.Patches) 900 901 // validate that metrics binding was not created as expected 902 v.validateNoMetricsBinding(t) 903 } 904 905 // TestHandleNamespaceAnnotation tests the handling of a namespace that specifies a template 906 // GIVEN a call to the webhook Handle function 907 // WHEN the namespace has a metrics template reference 908 // THEN the Handle function should succeed and a metricsBinding is created 909 func TestHandleNamespaceAnnotation(t *testing.T) { 910 911 tests := []struct { 912 name string 913 metricsBindingExists bool 914 expectAllowed bool 915 expectMetricsBindingUpdate bool 916 }{ 917 {"legacy app with existing MetricsBinding", true, true, true}, 918 {"new app with no MetricsBinding present", false, true, false}, 919 } 920 for _, tt := range tests { 921 t.Run(tt.name, func(t *testing.T) { 922 v := newGeneratorWorkloadWebhook() 923 924 // Test data 925 v.createNamespace(t, testNS, map[string]string{vzconst.VerrazzanoManagedLabelKey: "true"}, map[string]string{MetricsAnnotation: "testTemplateDifferentNamespace"}) 926 v.createNamespace(t, constants.VerrazzanoSystemNamespace, nil, nil) 927 v.createConfigMap(t, testNS, testConfigMapName) 928 testDeployment := appsv1.Deployment{ 929 TypeMeta: metav1.TypeMeta{ 930 Kind: vzconst.DeploymentWorkloadKind, 931 APIVersion: appsv1APIVersion, 932 }, 933 ObjectMeta: metav1.ObjectMeta{ 934 Name: testDeploymentName, 935 Namespace: testNS, 936 }, 937 } 938 assert.NoError(t, v.Client.Create(context.TODO(), &testDeployment)) 939 940 if tt.metricsBindingExists { 941 existingMetricsBinding := vzapp.MetricsBinding{ 942 ObjectMeta: metav1.ObjectMeta{ 943 Name: generateMetricsBindingName(testDeployment.Name, testDeployment.APIVersion, testDeployment.Kind), 944 Namespace: testNS, 945 }, 946 } 947 assert.NoError(t, v.Client.Create(context.TODO(), &existingMetricsBinding)) 948 } 949 950 testTemplate := vzapp.MetricsTemplate{ 951 ObjectMeta: metav1.ObjectMeta{ 952 Namespace: constants.VerrazzanoSystemNamespace, 953 Name: "testTemplateDifferentNamespace", 954 }, 955 Spec: vzapp.MetricsTemplateSpec{ 956 WorkloadSelector: vzapp.WorkloadSelector{}, 957 PrometheusConfig: vzapp.PrometheusConfig{ 958 TargetConfigMap: vzapp.TargetConfigMap{ 959 Namespace: testNS, 960 Name: testConfigMapName, 961 }, 962 }, 963 }, 964 } 965 extraTemplate := vzapp.MetricsTemplate{ 966 ObjectMeta: metav1.ObjectMeta{ 967 Namespace: constants.VerrazzanoSystemNamespace, 968 Name: "testWrongTemplate", 969 }, 970 Spec: vzapp.MetricsTemplateSpec{ 971 WorkloadSelector: vzapp.WorkloadSelector{}, 972 PrometheusConfig: vzapp.PrometheusConfig{ 973 TargetConfigMap: vzapp.TargetConfigMap{ 974 Namespace: testNS, 975 Name: testConfigMapName, 976 }, 977 }, 978 }, 979 } 980 assert.NoError(t, v.Client.Create(context.TODO(), &testTemplate)) 981 assert.NoError(t, v.Client.Create(context.TODO(), &extraTemplate)) 982 983 req := newGeneratorWorkloadRequest(admissionv1.Create, vzconst.DeploymentWorkloadKind, testDeployment) 984 res := v.Handle(context.TODO(), req) 985 assert.Equal(t, tt.expectAllowed, res.Allowed) 986 if tt.expectMetricsBindingUpdate { 987 assert.Len(t, res.Patches, 1) 988 assert.Equal(t, "add", res.Patches[0].Operation) 989 assert.Equal(t, "/metadata/labels", res.Patches[0].Path) 990 assert.Contains(t, res.Patches[0].Value, constants.MetricsWorkloadLabel) 991 992 // validate that metrics binding was created as expected 993 v.validateMetricsBinding(t, vzconst.VerrazzanoSystemNamespace, "testTemplateDifferentNamespace", testNS, testConfigMapName, false) 994 } else { 995 assert.Len(t, res.Patches, 0) 996 v.validateNoMetricsBinding(t) 997 } 998 }) 999 } 1000 } 1001 1002 // TestExistingMetricsBindingVmiConfigMap tests the handling of an existing metrics binding which 1003 // uses the legacy VMI Prometheus ConfigMap 1004 // GIVEN an existing metrics binding which uses the legacy VMI Prometheus ConfigMap 1005 // WHEN the webhook Handle is called 1006 // THEN the metrics binding should be modified to use the Prometheus Operator additionalScrapeConfigs secret instead 1007 func TestExistingMetricsBindingVmiConfigMap(t *testing.T) { 1008 1009 v := newGeneratorWorkloadWebhook() 1010 1011 // Test data 1012 v.createNamespace(t, testNS, map[string]string{vzconst.VerrazzanoManagedLabelKey: "true"}, map[string]string{MetricsAnnotation: "testTemplateDifferentNamespace"}) 1013 v.createNamespace(t, constants.VerrazzanoSystemNamespace, nil, nil) 1014 1015 testTemplate := vzapp.MetricsTemplate{ 1016 ObjectMeta: metav1.ObjectMeta{ 1017 Namespace: testNS, 1018 Name: "testTemplate", 1019 }, 1020 Spec: vzapp.MetricsTemplateSpec{ 1021 WorkloadSelector: vzapp.WorkloadSelector{}, 1022 PrometheusConfig: vzapp.PrometheusConfig{ 1023 TargetConfigMap: vzapp.TargetConfigMap{ 1024 Namespace: vzconst.VerrazzanoSystemNamespace, 1025 Name: vzconst.VmiPromConfigName, 1026 }, 1027 }, 1028 }, 1029 } 1030 assert.NoError(t, v.Client.Create(context.TODO(), &testTemplate)) 1031 1032 testDeployment := appsv1.Deployment{ 1033 TypeMeta: metav1.TypeMeta{ 1034 Kind: vzconst.DeploymentWorkloadKind, 1035 APIVersion: appsv1APIVersion, 1036 }, 1037 ObjectMeta: metav1.ObjectMeta{ 1038 Name: testDeploymentName, 1039 Namespace: testNS, 1040 Annotations: map[string]string{ 1041 MetricsAnnotation: testTemplate.Name, 1042 }, 1043 }, 1044 } 1045 assert.NoError(t, v.Client.Create(context.TODO(), &testDeployment)) 1046 1047 existingMetricsBinding := vzapp.MetricsBinding{ 1048 ObjectMeta: metav1.ObjectMeta{ 1049 Name: generateMetricsBindingName(testDeployment.Name, testDeployment.APIVersion, testDeployment.Kind), 1050 Namespace: testNS, 1051 }, 1052 Spec: vzapp.MetricsBindingSpec{ 1053 PrometheusConfigMap: vzapp.NamespaceName{ 1054 Namespace: vzconst.VerrazzanoSystemNamespace, 1055 Name: vzconst.VmiPromConfigName, 1056 }, 1057 }, 1058 } 1059 assert.NoError(t, v.Client.Create(context.TODO(), &existingMetricsBinding)) 1060 1061 req := newGeneratorWorkloadRequest(admissionv1.Update, vzconst.DeploymentWorkloadKind, testDeployment) 1062 res := v.Handle(context.TODO(), req) 1063 assert.True(t, res.Allowed, "Expected validation to succeed.") 1064 v.validateMetricsBinding(t, testNS, "testTemplate", "", "", true) 1065 } 1066 1067 func (v *WorkloadWebhook) createNamespace(t *testing.T, name string, labels map[string]string, annotations map[string]string) { 1068 ns := &corev1.Namespace{ 1069 ObjectMeta: metav1.ObjectMeta{ 1070 Name: name, 1071 Labels: labels, 1072 Annotations: annotations, 1073 }, 1074 } 1075 err := v.Client.Create(context.TODO(), ns) 1076 assert.NoError(t, err, "unexpected error creating namespace") 1077 _, err = v.KubeClient.CoreV1().Namespaces().Create(context.TODO(), ns, metav1.CreateOptions{}) 1078 assert.NoError(t, err, "unexpected error creating namespace") 1079 } 1080 1081 func (v *WorkloadWebhook) createConfigMap(t *testing.T, namespace string, name string) { 1082 cm := &corev1.ConfigMap{ 1083 ObjectMeta: metav1.ObjectMeta{ 1084 Namespace: namespace, 1085 Name: name, 1086 }, 1087 } 1088 _, err := v.KubeClient.CoreV1().ConfigMaps(namespace).Create(context.TODO(), cm, metav1.CreateOptions{}) 1089 assert.NoError(t, err, "unexpected error creating namespace") 1090 } 1091 1092 func (v *WorkloadWebhook) validateNoMetricsBinding(t *testing.T) { 1093 namespacedName := types.NamespacedName{Namespace: testNS, Name: "testDeployment-deployment"} 1094 metricsBinding := &vzapp.MetricsBinding{} 1095 assert.EqualError(t, v.Client.Get(context.TODO(), namespacedName, metricsBinding), "metricsbindings.app.verrazzano.io \"testDeployment-deployment\" not found") 1096 } 1097 1098 func (v *WorkloadWebhook) validateMetricsBinding( 1099 t *testing.T, templateNamespace string, templateName string, configMapNamespace string, 1100 configMapName string, hasAdditionalScrapeConfigsSecret bool) { 1101 namespacedName := types.NamespacedName{Namespace: testNS, Name: "testDeployment-apps-v1-deployment"} 1102 metricsBinding := &vzapp.MetricsBinding{} 1103 assert.NoError(t, v.Client.Get(context.TODO(), namespacedName, metricsBinding)) 1104 assert.Equal(t, appsv1APIVersion, metricsBinding.Spec.Workload.TypeMeta.APIVersion) 1105 assert.Equal(t, vzconst.DeploymentWorkloadKind, metricsBinding.Spec.Workload.TypeMeta.Kind) 1106 assert.Equal(t, testDeploymentName, metricsBinding.Spec.Workload.Name) 1107 assert.Equal(t, templateNamespace, metricsBinding.Spec.MetricsTemplate.Namespace) 1108 assert.Equal(t, templateName, metricsBinding.Spec.MetricsTemplate.Name) 1109 assert.Equal(t, configMapNamespace, metricsBinding.Spec.PrometheusConfigMap.Namespace) 1110 assert.Equal(t, configMapName, metricsBinding.Spec.PrometheusConfigMap.Name) 1111 if hasAdditionalScrapeConfigsSecret { 1112 assert.Equal(t, vzconst.PrometheusOperatorNamespace, metricsBinding.Spec.PrometheusConfigSecret.Namespace) 1113 assert.Equal(t, vzconst.PromAdditionalScrapeConfigsSecretName, metricsBinding.Spec.PrometheusConfigSecret.Name) 1114 assert.Equal(t, vzconst.PromAdditionalScrapeConfigsSecretKey, metricsBinding.Spec.PrometheusConfigSecret.Key) 1115 } 1116 }