From f50dddd3636431612b03372f0fa6fd20116ee8ad Mon Sep 17 00:00:00 2001 From: Benedikt Bongartz Date: Fri, 25 Mar 2022 16:56:53 +0100 Subject: [PATCH] port reconciler tests to webhook Signed-off-by: Benedikt Bongartz --- controllers/appsv1/deployment_webhook_test.go | 217 ++++++++++++++++++ .../jaegertracing/jaeger_controller_test.go | 122 ++++++++++ 2 files changed, 339 insertions(+) create mode 100644 controllers/appsv1/deployment_webhook_test.go diff --git a/controllers/appsv1/deployment_webhook_test.go b/controllers/appsv1/deployment_webhook_test.go new file mode 100644 index 0000000000..3740a2a3ef --- /dev/null +++ b/controllers/appsv1/deployment_webhook_test.go @@ -0,0 +1,217 @@ +package appsv1 + +import ( + "bytes" + "context" + "encoding/json" + "testing" + + "github.com/spf13/viper" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + jsonpatch "gomodules.xyz/jsonpatch/v2" + admissionv1 "k8s.io/api/admission/v1" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + + v1 "github.com/jaegertracing/jaeger-operator/apis/v1" + "github.com/jaegertracing/jaeger-operator/pkg/inject" +) + +func TestReconcileConfigMaps(t *testing.T) { + testCases := []struct { + desc string + existing []runtime.Object + }{ + { + desc: "all config maps missing", + }, + { + desc: "none missing", + existing: []runtime.Object{ + &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns1", + Name: "my-instance-trusted-ca", + }, + }, + &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns1", + Name: "my-instance-service-ca", + }, + }, + }, + }, + } + for _, tC := range testCases { + t.Run(tC.desc, func(t *testing.T) { + // prepare + jaeger := v1.NewJaeger(types.NamespacedName{ + Namespace: "observability", + Name: "my-instance", + }) + dep := appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "ns1", + Name: "my-dep", + }, + } + + cl := fake.NewFakeClient(tC.existing...) + + viper.Set("platform", v1.FlagPlatformOpenShift) + defer viper.Reset() + + // test + err := reconcileConfigMaps(context.Background(), cl, jaeger, &dep) + + // verify + assert.NoError(t, err) + + cms := corev1.ConfigMapList{} + err = cl.List(context.Background(), &cms) + require.NoError(t, err) + + assert.Len(t, cms.Items, 2) + }) + } +} + +func TestReconcilieDeployment(t *testing.T) { + namespacedName := types.NamespacedName{ + Name: "jaeger-query", + Namespace: "my-ns", + } + + jaeger := v1.NewJaeger(types.NamespacedName{ + Namespace: "observability", + Name: "my-instance", + }) + + s := scheme.Scheme + s.AddKnownTypes(v1.GroupVersion, jaeger) + s.AddKnownTypes(v1.GroupVersion, &v1.JaegerList{}) + + testCases := []struct { + desc string + dep *appsv1.Deployment + resp admission.Response + }{ + { + desc: "Should not remove the instance from a jaeger component", + dep: inject.Sidecar(jaeger, &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: namespacedName.Name, + Namespace: namespacedName.Namespace, + Annotations: map[string]string{}, + Labels: map[string]string{ + "app": "jaeger", + }, + }, + Spec: appsv1.DeploymentSpec{ + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{}, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Name: "only_container", + }}, + }, + }, + }, + }), + resp: admission.Response{ + AdmissionResponse: admissionv1.AdmissionResponse{ + Allowed: true, + Result: &metav1.Status{ + Reason: "is jeager deployment, we do not touch it", + Code: 200, + }, + }, + }, + }, + { + desc: "Should remove the instance", + dep: inject.Sidecar(jaeger, &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: namespacedName.Name, + Namespace: namespacedName.Namespace, + Annotations: map[string]string{}, + }, + Spec: appsv1.DeploymentSpec{ + Template: corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{}, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{{ + Name: "only_container", + }}, + }, + }, + }, + }), + resp: admission.Response{ + Patches: []jsonpatch.JsonPatchOperation{ + { + Operation: "remove", + Path: "/metadata/labels", + }, + { + Operation: "remove", + Path: "/spec/template/spec/containers/1", + }, + }, + AdmissionResponse: admissionv1.AdmissionResponse{ + Allowed: true, + PatchType: func() *admissionv1.PatchType { str := admissionv1.PatchTypeJSONPatch; return &str }(), + }, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.desc, func(t *testing.T) { + + assert.Equal(t, 2, len(tc.dep.Spec.Template.Spec.Containers)) + ns := &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: namespacedName.Namespace, + }, + } + + cl := fake.NewFakeClient(tc.dep, ns) + + r := NewDeploymentInterceptorWebhook(cl) + + req := admission.Request{ + AdmissionRequest: admissionv1.AdmissionRequest{ + Name: tc.dep.Name, + Namespace: tc.dep.Namespace, + Object: runtime.RawExtension{ + Raw: func() []byte { + var buf bytes.Buffer + if getErr := json.NewEncoder(&buf).Encode(tc.dep); getErr != nil { + t.Fatal(getErr) + } + return buf.Bytes() + }(), + }, + }, + } + + decoder, err := admission.NewDecoder(s) + require.NoError(t, err) + admission.InjectDecoderInto(decoder, r) + resp := r.Handle(context.Background(), req) + + assert.Equal(t, tc.resp, resp) + + require.NoError(t, err) + }) + } +} diff --git a/controllers/jaegertracing/jaeger_controller_test.go b/controllers/jaegertracing/jaeger_controller_test.go index be4899383c..d5c8b79618 100644 --- a/controllers/jaegertracing/jaeger_controller_test.go +++ b/controllers/jaegertracing/jaeger_controller_test.go @@ -2,19 +2,141 @@ package jaegertracing_test import ( "context" + "sort" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/client" k8sconfig "sigs.k8s.io/controller-runtime/pkg/client/config" + "sigs.k8s.io/controller-runtime/pkg/client/fake" "sigs.k8s.io/controller-runtime/pkg/manager" k8sreconcile "sigs.k8s.io/controller-runtime/pkg/reconcile" v1 "github.com/jaegertracing/jaeger-operator/apis/v1" "github.com/jaegertracing/jaeger-operator/controllers/jaegertracing" + ctljaeger "github.com/jaegertracing/jaeger-operator/pkg/controller/jaeger" + "github.com/jaegertracing/jaeger-operator/pkg/inject" ) +type updateCounter struct { + client.WithWatch + + counter int +} + +func (u *updateCounter) Update(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error { + u.counter++ + return u.WithWatch.Update(ctx, obj, opts...) +} + +func TestSyncOnJaegerChanges(t *testing.T) { + // prepare + jaeger := v1.NewJaeger(types.NamespacedName{ + Namespace: "observability", + Name: "my-instance", + }) + + objs := []runtime.Object{ + &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{ + Name: "ns-with-annotation", + Annotations: map[string]string{ + inject.Annotation: "true", + }, + }}, + &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "dep-without-annotation", + Namespace: "ns-with-annotation", + }, + }, + &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "dep-with-annotation", + Namespace: "ns-with-annotation", + Annotations: map[string]string{ + inject.Annotation: "true", + }, + }, + }, + + &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{ + Name: "ns-without-annotation", + }}, + &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "dep-without-annotation", + Namespace: "ns-without-annotation", + }, + }, + &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "dep-with-annotation", + Namespace: "ns-without-annotation", + Annotations: map[string]string{ + inject.Annotation: "true", + }, + }, + }, + &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "dep-with-another-jaegers-label", + Namespace: "ns-without-annotation", + Annotations: map[string]string{ + inject.Annotation: "true", + }, + Labels: map[string]string{ + inject.Label: "some-other-jaeger", + }, + }, + }, + &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Name: "dep-affected-jaeger-label", + Namespace: "ns-without-annotation", + Annotations: map[string]string{ + inject.Annotation: "true", + }, + Labels: map[string]string{ + inject.Label: jaeger.Name, + }, + }, + }, + } + + s := scheme.Scheme + cl := &updateCounter{WithWatch: fake.NewFakeClient(objs...)} + r := ctljaeger.New(cl, cl, s) + + // test + requests := r.SyncOnJaegerChanges(jaeger) + + // verify + assert.Equal(t, cl.counter, 3) + assert.Len(t, requests, 1) + + expected := []k8sreconcile.Request{ + {NamespacedName: types.NamespacedName{ + Name: "dep-without-annotation", + Namespace: "ns-with-annotation", + }}, + } + + sort.Slice(requests, func(i, j int) bool { + return requests[i].NamespacedName.String() < requests[j].NamespacedName.String() + }) + sort.Slice(expected, func(i, j int) bool { + return expected[i].NamespacedName.String() < expected[j].NamespacedName.String() + }) + assert.Equal(t, expected, requests) +} + func TestNewJaegerInstance(t *testing.T) { // prepare nsn := types.NamespacedName{Name: "my-instance", Namespace: "default"}