github.com/verrazzano/verrazzano@v1.7.0/application-operator/controllers/webhooks/metrics-binding-labeler-pod_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 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  	appsv1 "k8s.io/api/apps/v1"
    15  	corev1 "k8s.io/api/core/v1"
    16  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    17  	"k8s.io/apimachinery/pkg/runtime"
    18  	"k8s.io/apimachinery/pkg/runtime/schema"
    19  	"k8s.io/client-go/dynamic/fake"
    20  	ctrlfake "sigs.k8s.io/controller-runtime/pkg/client/fake"
    21  	"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
    22  )
    23  
    24  // newLabelerPodWebhook creates a new LabelerPodWebhook
    25  func newLabelerPodWebhook() LabelerPodWebhook {
    26  	scheme := newScheme()
    27  	scheme.AddKnownTypes(schema.GroupVersion{
    28  		Version: "v1",
    29  	}, &corev1.Namespace{}, &corev1.Pod{}, &appsv1.Deployment{}, &appsv1.ReplicaSet{}, &appsv1.StatefulSet{})
    30  	_ = vzapp.AddToScheme(scheme)
    31  	decoder, _ := admission.NewDecoder(scheme)
    32  	cli := ctrlfake.NewClientBuilder().WithScheme(scheme).Build()
    33  	v := LabelerPodWebhook{
    34  		Client:        cli,
    35  		DynamicClient: fake.NewSimpleDynamicClient(runtime.NewScheme()),
    36  	}
    37  	_ = v.InjectDecoder(decoder)
    38  	return v
    39  }
    40  
    41  // TestNoOwnerReferences tests the handling of a Pod resource
    42  // GIVEN a call to the webhook Handle function
    43  // WHEN the pod resource has no owner references
    44  // THEN the Handle function should succeed and the pod is mutated
    45  func TestNoOwnerReferences(t *testing.T) {
    46  	a := newLabelerPodWebhook()
    47  
    48  	// Test data
    49  	pod := corev1.Pod{
    50  		ObjectMeta: metav1.ObjectMeta{
    51  			Name:      "test",
    52  			Namespace: "default",
    53  		},
    54  	}
    55  	assert.NoError(t, a.Client.Create(context.TODO(), &pod))
    56  
    57  	req := admission.Request{}
    58  	req.Namespace = "default"
    59  	marshaledPod, err := json.Marshal(pod)
    60  	assert.NoError(t, err, "Unexpected error marshaling pod")
    61  	req.Object = runtime.RawExtension{Raw: marshaledPod}
    62  	res := a.Handle(context.TODO(), req)
    63  
    64  	verifyResponse(t, res, 2)
    65  }
    66  
    67  // TestOwnerReference tests the handling of a Pod resource
    68  // GIVEN a call to the webhook Handle function
    69  // WHEN the pod resource has one owner reference
    70  // THEN the Handle function should succeed and the pod is mutated
    71  func TestOwnerReference(t *testing.T) {
    72  	a := newLabelerPodWebhook()
    73  
    74  	// Create a replica set with no owner reference
    75  	u := newUnstructured("apps/v1", "ReplicaSet", "test-replicaSet")
    76  	resource := schema.GroupVersionResource{
    77  		Group:    "apps",
    78  		Version:  "v1",
    79  		Resource: "replicasets",
    80  	}
    81  	u.SetLabels(map[string]string{constants.MetricsWorkloadLabel: "testValue"})
    82  	_, err := a.DynamicClient.Resource(resource).Namespace("default").Create(context.TODO(), u, metav1.CreateOptions{})
    83  	assert.NoError(t, err, "Unexpected error creating replica set")
    84  
    85  	// Create the pod with an owner reference
    86  	pod := corev1.Pod{
    87  		ObjectMeta: metav1.ObjectMeta{
    88  			Name:      "test",
    89  			Namespace: "default",
    90  			OwnerReferences: []metav1.OwnerReference{
    91  				{
    92  					Name:       "test-replicaSet",
    93  					Kind:       "ReplicaSet",
    94  					APIVersion: "apps/v1",
    95  				},
    96  			},
    97  		},
    98  	}
    99  	assert.NoError(t, a.Client.Create(context.TODO(), &pod))
   100  
   101  	req := admission.Request{}
   102  	req.Namespace = "default"
   103  	marshaledPod, err := json.Marshal(pod)
   104  	assert.NoError(t, err, "Unexpected error marshaling pod")
   105  	req.Object = runtime.RawExtension{Raw: marshaledPod}
   106  	res := a.Handle(context.TODO(), req)
   107  
   108  	verifyResponse(t, res, 2)
   109  }
   110  
   111  // TestMultipleOwnerReference tests the handling of a Pod resource
   112  // GIVEN a call to the webhook Handle function
   113  // WHEN the pod resource has nested owner references and the 2nd owner reference
   114  // is the workload resource
   115  // THEN the Handle function should succeed and the pod is mutated
   116  func TestMultipleOwnerReference(t *testing.T) {
   117  	a := newLabelerPodWebhook()
   118  
   119  	// Create a deployment with no owner reference
   120  	u := newUnstructured("apps/v1", "Deployment", "test-deployment")
   121  	resource := schema.GroupVersionResource{
   122  		Group:    "apps",
   123  		Version:  "v1",
   124  		Resource: "deployments",
   125  	}
   126  	u.SetLabels(map[string]string{constants.MetricsWorkloadLabel: "testValue"})
   127  	_, err := a.DynamicClient.Resource(resource).Namespace("default").Create(context.TODO(), u, metav1.CreateOptions{})
   128  	assert.NoError(t, err, "Unexpected error creating deployment")
   129  
   130  	// Create a replica set with an owner reference
   131  	u = newUnstructured("apps/v1", "ReplicaSet", "test-replicaSet")
   132  	resource = schema.GroupVersionResource{
   133  		Group:    "apps",
   134  		Version:  "v1",
   135  		Resource: "replicasets",
   136  	}
   137  	ownerReferences := []metav1.OwnerReference{
   138  		{
   139  			Name:       "test-deployment",
   140  			Kind:       "Deployment",
   141  			APIVersion: "apps/v1",
   142  		},
   143  	}
   144  	u.SetOwnerReferences(ownerReferences)
   145  	_, err = a.DynamicClient.Resource(resource).Namespace("default").Create(context.TODO(), u, metav1.CreateOptions{})
   146  	assert.NoError(t, err, "Unexpected error creating replica set")
   147  
   148  	// Create the pod with an owner reference
   149  	pod := corev1.Pod{
   150  		ObjectMeta: metav1.ObjectMeta{
   151  			Name:      "test",
   152  			Namespace: "default",
   153  			OwnerReferences: []metav1.OwnerReference{
   154  				{
   155  					Name:       "test-replicaSet",
   156  					Kind:       "ReplicaSet",
   157  					APIVersion: "apps/v1",
   158  				},
   159  			},
   160  		},
   161  	}
   162  	assert.NoError(t, a.Client.Create(context.TODO(), &pod))
   163  
   164  	req := admission.Request{}
   165  	req.Namespace = "default"
   166  	marshaledPod, err := json.Marshal(pod)
   167  	assert.NoError(t, err, "Unexpected error marshaling pod")
   168  	req.Object = runtime.RawExtension{Raw: marshaledPod}
   169  	res := a.Handle(context.TODO(), req)
   170  
   171  	verifyResponse(t, res, 2)
   172  }
   173  
   174  // TestMultipleOwnerReferenceAndWorkloadResources tests the handling of a Pod resource
   175  // GIVEN a call to the webhook Handle function
   176  // WHEN the pod resource has nested owner references and two owner references are found to be
   177  // a workload resource
   178  // THEN the Handle function should fail and return an error
   179  func TestMultipleOwnerReferenceAndWorkloadResources(t *testing.T) {
   180  	a := newLabelerPodWebhook()
   181  
   182  	// Create a deployment with no owner reference
   183  	u := newUnstructured("apps/v1", "Deployment", "test-deployment")
   184  	resource := schema.GroupVersionResource{
   185  		Group:    "apps",
   186  		Version:  "v1",
   187  		Resource: "deployments",
   188  	}
   189  	u.SetLabels(map[string]string{constants.MetricsWorkloadLabel: "testValue"})
   190  	_, err := a.DynamicClient.Resource(resource).Namespace("default").Create(context.TODO(), u, metav1.CreateOptions{})
   191  	assert.NoError(t, err, "Unexpected error creating deployment")
   192  
   193  	// Create another deployment with no owner reference
   194  	u = newUnstructured("apps/v1", "Deployment", "test-deployment2")
   195  	resource = schema.GroupVersionResource{
   196  		Group:    "apps",
   197  		Version:  "v1",
   198  		Resource: "deployments",
   199  	}
   200  	u.SetLabels(map[string]string{constants.MetricsWorkloadLabel: "testValue"})
   201  	_, err = a.DynamicClient.Resource(resource).Namespace("default").Create(context.TODO(), u, metav1.CreateOptions{})
   202  	assert.NoError(t, err, "Unexpected error creating deployment")
   203  
   204  	// Create a replica set with two owner references
   205  	u = newUnstructured("apps/v1", "ReplicaSet", "test-replicaSet")
   206  	resource = schema.GroupVersionResource{
   207  		Group:    "apps",
   208  		Version:  "v1",
   209  		Resource: "replicasets",
   210  	}
   211  	ownerReferences := []metav1.OwnerReference{
   212  		{
   213  			Name:       "test-deployment",
   214  			Kind:       "Deployment",
   215  			APIVersion: "apps/v1",
   216  		},
   217  		{
   218  			Name:       "test-deployment2",
   219  			Kind:       "Deployment",
   220  			APIVersion: "apps/v1",
   221  		},
   222  	}
   223  	u.SetOwnerReferences(ownerReferences)
   224  	_, err = a.DynamicClient.Resource(resource).Namespace("default").Create(context.TODO(), u, metav1.CreateOptions{})
   225  	assert.NoError(t, err, "Unexpected error creating replica set")
   226  
   227  	// Create the pod with an owner reference
   228  	pod := corev1.Pod{
   229  		ObjectMeta: metav1.ObjectMeta{
   230  			Name:      "test",
   231  			Namespace: "default",
   232  			OwnerReferences: []metav1.OwnerReference{
   233  				{
   234  					Name:       "test-replicaSet",
   235  					Kind:       "ReplicaSet",
   236  					APIVersion: "apps/v1",
   237  				},
   238  			},
   239  		},
   240  	}
   241  	assert.NoError(t, a.Client.Create(context.TODO(), &pod))
   242  
   243  	req := admission.Request{}
   244  	req.Namespace = "default"
   245  	marshaledPod, err := json.Marshal(pod)
   246  	assert.NoError(t, err, "Unexpected error marshaling pod")
   247  	req.Object = runtime.RawExtension{Raw: marshaledPod}
   248  	res := a.Handle(context.TODO(), req)
   249  
   250  	assert.False(t, res.Allowed)
   251  	assert.Equal(t, "multiple workload resources found for test, Verrazzano metrics cannot be enabled", res.Result.Message)
   252  }
   253  
   254  // TestPodPrometheusAnnotations tests the annotation of a Pod resource
   255  // GIVEN a call to the webhook Handle function
   256  // WHEN the pod has Prometheus annotations
   257  // THEN the Handle function should not overwrite those annotations
   258  func TestPodPrometheusAnnotations(t *testing.T) {
   259  	a := newLabelerPodWebhook()
   260  
   261  	// Test data
   262  	pod := corev1.Pod{
   263  		ObjectMeta: metav1.ObjectMeta{
   264  			Name:      "test",
   265  			Namespace: "default",
   266  			Annotations: map[string]string{
   267  				PrometheusPortAnnotation:   PrometheusPortDefault,
   268  				PrometheusPathAnnotation:   PrometheusPathAnnotation,
   269  				PrometheusScrapeAnnotation: PrometheusScrapeAnnotation,
   270  			},
   271  		},
   272  	}
   273  	assert.NoError(t, a.Client.Create(context.TODO(), &pod))
   274  
   275  	req := admission.Request{}
   276  	req.Namespace = "default"
   277  	marshaledPod, err := json.Marshal(pod)
   278  	assert.NoError(t, err, "Unexpected error marshaling pod")
   279  	req.Object = runtime.RawExtension{Raw: marshaledPod}
   280  	res := a.Handle(context.TODO(), req)
   281  
   282  	verifyResponse(t, res, 1)
   283  }
   284  
   285  func verifyResponse(t *testing.T, res admission.Response, len int) {
   286  	assert.True(t, res.Allowed)
   287  	assert.Len(t, res.Patches, len)
   288  	for _, patch := range res.Patches {
   289  		assert.Equal(t, "add", patch.Operation)
   290  		assert.True(t, patch.Path == "/metadata/labels" || patch.Path == "/metadata/annotations")
   291  	}
   292  }