From be4e4c7452f793c9a5703319cb141b0cb3faf33b Mon Sep 17 00:00:00 2001 From: odubajDT Date: Wed, 8 Mar 2023 08:37:08 +0100 Subject: [PATCH 1/4] test: introduce component test for KeptnAppVersion controller Signed-off-by: odubajDT --- .../component/appversioncontroller_test.go | 210 ++++++++++++++++++ 1 file changed, 210 insertions(+) create mode 100644 operator/test/component/appversioncontroller_test.go diff --git a/operator/test/component/appversioncontroller_test.go b/operator/test/component/appversioncontroller_test.go new file mode 100644 index 0000000000..ea34eb33f0 --- /dev/null +++ b/operator/test/component/appversioncontroller_test.go @@ -0,0 +1,210 @@ +package component + +import ( + "context" + "strings" + "time" + + klcv1alpha3 "github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha3" + apicommon "github.com/keptn/lifecycle-toolkit/operator/apis/lifecycle/v1alpha3/common" + controllercommon "github.com/keptn/lifecycle-toolkit/operator/controllers/common" + "github.com/keptn/lifecycle-toolkit/operator/controllers/lifecycle/interfaces" + "github.com/keptn/lifecycle-toolkit/operator/controllers/lifecycle/keptnappversion" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + otelsdk "go.opentelemetry.io/otel/sdk/trace" + sdktest "go.opentelemetry.io/otel/sdk/trace/tracetest" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apiserver/pkg/storage/names" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// clean example of component test (E2E test/ integration test can be achieved adding a real cluster) +// App controller creates AppVersion when a new App CRD is added +// span for creation and reconcile are correct +// container must be ordered to have the before all setup +// this way the container spec check is not randomized, so we can make +// assertions on spans number and traces +var _ = Describe("KeptnAppVersionController", Ordered, func() { + var ( + appName string + namespace string + version string + spanRecorder *sdktest.SpanRecorder + tracer *otelsdk.TracerProvider + ) + + BeforeAll(func() { + //setup once + By("Waiting for Manager") + Eventually(func() bool { + return k8sManager != nil + }).Should(Equal(true)) + + By("Creating the Controller") + + spanRecorder = sdktest.NewSpanRecorder() + tracer = otelsdk.NewTracerProvider(otelsdk.WithSpanProcessor(spanRecorder)) + + ////setup controllers here + controllers := []interfaces.Controller{&keptnappversion.KeptnAppVersionReconciler{ + Client: k8sManager.GetClient(), + Scheme: k8sManager.GetScheme(), + Recorder: k8sManager.GetEventRecorderFor("test-appversion-controller"), + Log: GinkgoLogr, + Meters: initKeptnMeters(), + SpanHandler: &controllercommon.SpanHandler{}, + TracerFactory: &tracerFactory{tracer: tracer}, + }} + setupManager(controllers) // we can register multiple time the same controller + // so that they have a different span/trace + }) + + BeforeEach(func() { // list var here they avll be copied for every spec + appName = names.SimpleNameGenerator.GenerateName("test-appversion-reconciler-") + namespace = "default" // namespaces are not deleted in the api so be careful + // when creating you can use ignoreAlreadyExists(err error) + version = "1.0.0" + }) + Describe("Creation of AppVersion", func() { + var ( + av *klcv1alpha3.KeptnAppVersion + ) + Context("reconcile a new AppVersions CRD", func() { + + BeforeEach(func() { + }) + + It("should be deprecated when pre-eval checks failed", func() { + evaluation := &klcv1alpha3.KeptnEvaluation{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pre-eval-eval-def-appversion", + Namespace: namespace, + }, + Spec: klcv1alpha3.KeptnEvaluationSpec{ + EvaluationDefinition: "eval-def-appversion", + AppName: appName, + AppVersion: version, + Type: apicommon.PreDeploymentEvaluationCheckType, + Retries: 10, + }, + } + + defer func() { + _ = k8sClient.Delete(ctx, evaluation) + }() + + By("Creating Evaluation") + err := k8sClient.Create(context.TODO(), evaluation) + Expect(err).To(BeNil()) + + evaluation.Status = klcv1alpha3.KeptnEvaluationStatus{ + OverallStatus: apicommon.StateFailed, + RetryCount: 10, + EvaluationStatus: map[string]klcv1alpha3.EvaluationStatusItem{ + "something": { + Status: apicommon.StateFailed, + Value: "10", + }, + }, + StartTime: metav1.Time{Time: time.Now().UTC()}, + EndTime: metav1.Time{Time: time.Now().UTC().Add(5 * time.Second)}, + } + + err = k8sClient.Status().Update(ctx, evaluation) + Expect(err).To(BeNil()) + + av = &klcv1alpha3.KeptnAppVersion{ + ObjectMeta: metav1.ObjectMeta{ + Name: appName + "-" + version, + Namespace: namespace, + }, + Spec: klcv1alpha3.KeptnAppVersionSpec{ + AppName: appName, + KeptnAppSpec: klcv1alpha3.KeptnAppSpec{ + Version: version, + PreDeploymentEvaluations: []string{"eval-def-appversion"}, + }, + }, + } + By("Creating AppVersion") + err = k8sClient.Create(context.TODO(), av) + Expect(err).To(BeNil()) + + time.Sleep(5 * time.Second) + + av2 := &klcv1alpha3.KeptnAppVersion{} + err = k8sClient.Get(ctx, types.NamespacedName{Namespace: av.Namespace, Name: av.Name}, av2) + Expect(err).To(BeNil()) + Expect(av2).To(Not(BeNil())) + + av2.Status = klcv1alpha3.KeptnAppVersionStatus{ + PreDeploymentStatus: apicommon.StateSucceeded, + PreDeploymentEvaluationStatus: apicommon.StateProgressing, + WorkloadOverallStatus: apicommon.StatePending, + PostDeploymentStatus: apicommon.StatePending, + PostDeploymentEvaluationStatus: apicommon.StatePending, + CurrentPhase: apicommon.PhaseWorkloadPreEvaluation.ShortName, + Status: apicommon.StateProgressing, + PreDeploymentEvaluationTaskStatus: []klcv1alpha3.ItemStatus{ + { + Name: "pre-eval-eval-def-appversion", + Status: apicommon.StateProgressing, + DefinitionName: "eval-def-appversion", + }, + }, + } + + err = k8sClient.Status().Update(ctx, av2) + Expect(err).To(BeNil()) + + By("Ensuring all phases after pre-eval checks are deprecated") + avNameObj := types.NamespacedName{ + Namespace: av.Namespace, + Name: av.Name, + } + Eventually(func(g Gomega) { + av := &klcv1alpha3.KeptnAppVersion{} + err := k8sClient.Get(ctx, avNameObj, av) + g.Expect(err).To(BeNil()) + g.Expect(av).To(Not(BeNil())) + g.Expect(av.Status.PreDeploymentStatus).To(BeEquivalentTo(apicommon.StateSucceeded)) + g.Expect(av.Status.PreDeploymentEvaluationStatus).To(BeEquivalentTo(apicommon.StateFailed)) + g.Expect(av.Status.WorkloadOverallStatus).To(BeEquivalentTo(apicommon.StateDeprecated)) + g.Expect(av.Status.PostDeploymentStatus).To(BeEquivalentTo(apicommon.StateDeprecated)) + g.Expect(av.Status.PostDeploymentEvaluationStatus).To(BeEquivalentTo(apicommon.StateDeprecated)) + g.Expect(av.Status.Status).To(BeEquivalentTo(apicommon.StateFailed)) + }, "30s").Should(Succeed()) + + By("Ensuring that a K8s Event containing the reason for the failed evaluation has been sent") + + Eventually(func(g Gomega) { + eventList := &corev1.EventList{} + err := k8sClient.List(ctx, eventList, client.InNamespace(namespace)) + g.Expect(err).To(BeNil()) + + foundEvent := &corev1.Event{} + + for _, e := range eventList.Items { + if strings.Contains(e.Name, av.GetName()) && e.Reason == "AppPreDeployEvaluationsFailed" { + foundEvent = &e + break + } + } + g.Expect(foundEvent).NotTo(BeNil()) + }, "30s").Should(Succeed()) + }) + AfterEach(func() { + // Remember to clean up the cluster after each test + err := k8sClient.Delete(ctx, av) + logErrorIfPresent(err) + // Reset span recorder after each spec + resetSpanRecords(tracer, spanRecorder) + }) + + }) + + }) +}) From 92be4cc5be61bd6b2dc5da30d8727a2e45275ed1 Mon Sep 17 00:00:00 2001 From: odubajDT Date: Wed, 8 Mar 2023 08:44:52 +0100 Subject: [PATCH 2/4] fix linter issues Signed-off-by: odubajDT --- operator/test/component/appversioncontroller_test.go | 1 + operator/test/component/workloadinstancecontroller_test.go | 1 + 2 files changed, 2 insertions(+) diff --git a/operator/test/component/appversioncontroller_test.go b/operator/test/component/appversioncontroller_test.go index ea34eb33f0..ffe15ef6c5 100644 --- a/operator/test/component/appversioncontroller_test.go +++ b/operator/test/component/appversioncontroller_test.go @@ -165,6 +165,7 @@ var _ = Describe("KeptnAppVersionController", Ordered, func() { Namespace: av.Namespace, Name: av.Name, } + //nolint:dupl Eventually(func(g Gomega) { av := &klcv1alpha3.KeptnAppVersion{} err := k8sClient.Get(ctx, avNameObj, av) diff --git a/operator/test/component/workloadinstancecontroller_test.go b/operator/test/component/workloadinstancecontroller_test.go index e03bd045b1..ccbc5b80bb 100644 --- a/operator/test/component/workloadinstancecontroller_test.go +++ b/operator/test/component/workloadinstancecontroller_test.go @@ -384,6 +384,7 @@ var _ = Describe("KeptnWorkloadInstanceController", Ordered, func() { Namespace: wi.Namespace, Name: wi.Name, } + //nolint:dupl Eventually(func(g Gomega) { wi := &klcv1alpha3.KeptnWorkloadInstance{} err := k8sClient.Get(ctx, wiNameObj, wi) From 9bfc4cfd9966747caceb82b2ec072d8bc68fbc25 Mon Sep 17 00:00:00 2001 From: odubajDT Date: Wed, 8 Mar 2023 09:14:00 +0100 Subject: [PATCH 3/4] remove flakyness Signed-off-by: odubajDT --- .../component/appversioncontroller_test.go | 6 ++ .../workloadinstancecontroller_test.go | 87 +++++++++++++------ 2 files changed, 65 insertions(+), 28 deletions(-) diff --git a/operator/test/component/appversioncontroller_test.go b/operator/test/component/appversioncontroller_test.go index ffe15ef6c5..75b04dd44b 100644 --- a/operator/test/component/appversioncontroller_test.go +++ b/operator/test/component/appversioncontroller_test.go @@ -100,6 +100,12 @@ var _ = Describe("KeptnAppVersionController", Ordered, func() { err := k8sClient.Create(context.TODO(), evaluation) Expect(err).To(BeNil()) + err = k8sClient.Get(ctx, types.NamespacedName{ + Namespace: namespace, + Name: evaluation.Name, + }, evaluation) + Expect(err).To(BeNil()) + evaluation.Status = klcv1alpha3.KeptnEvaluationStatus{ OverallStatus: apicommon.StateFailed, RetryCount: 10, diff --git a/operator/test/component/workloadinstancecontroller_test.go b/operator/test/component/workloadinstancecontroller_test.go index ccbc5b80bb..edb2b673ce 100644 --- a/operator/test/component/workloadinstancecontroller_test.go +++ b/operator/test/component/workloadinstancecontroller_test.go @@ -18,6 +18,7 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + "k8s.io/apiserver/pkg/storage/names" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -47,7 +48,7 @@ func getPodTemplateSpec() corev1.PodTemplateSpec { // assertions on spans number and traces var _ = Describe("KeptnWorkloadInstanceController", Ordered, func() { var ( - name string + appName string namespace string version string spanRecorder *sdktest.SpanRecorder @@ -91,7 +92,7 @@ var _ = Describe("KeptnWorkloadInstanceController", Ordered, func() { }) BeforeEach(func() { // list var here they will be copied for every spec - name = "test-app" + appName = names.SimpleNameGenerator.GenerateName("test-app-") namespace = "default" // namespaces are not deleted in the api so be careful // when creating you can use ignoreAlreadyExists(err error) version = "1.0.0" @@ -104,19 +105,19 @@ var _ = Describe("KeptnWorkloadInstanceController", Ordered, func() { Context("with a new AppVersions CRD", func() { BeforeEach(func() { - appVersion = createAppVersionInCluster(name, namespace, version) + appVersion = createAppVersionInCluster(appName, namespace, version) }) It("should fail if Workload not found in AppVersion", func() { wiName := "not-found" wi = &klcv1alpha3.KeptnWorkloadInstance{ ObjectMeta: metav1.ObjectMeta{ - Name: name, + Name: appName, Namespace: namespace, }, Spec: klcv1alpha3.KeptnWorkloadInstanceSpec{ KeptnWorkloadSpec: klcv1alpha3.KeptnWorkloadSpec{}, - WorkloadName: "wi-test-app-wname-" + wiName, + WorkloadName: appName + "-wname-" + wiName, TraceId: map[string]string{"traceparent": "00-0f89f15e562489e2e171eca1cf9ba958-d2fa6dbbcbf7e29a-01"}, }, } @@ -165,15 +166,16 @@ var _ = Describe("KeptnWorkloadInstanceController", Ordered, func() { Expect(err).To(BeNil()) By("Setting the App PreDeploymentEvaluation Status to 'Succeeded'") - appVersion.Status.PreDeploymentEvaluationStatus = apicommon.StateSucceeded - err = k8sClient.Status().Update(ctx, appVersion) + + av := &klcv1alpha3.KeptnAppVersion{} + err = k8sClient.Get(ctx, types.NamespacedName{ + Namespace: namespace, + Name: appName, + }, av) Expect(err).To(BeNil()) - By("Bringing the StatefulSet into its ready state") - statefulSet.Status.AvailableReplicas = 1 - statefulSet.Status.ReadyReplicas = 1 - statefulSet.Status.Replicas = 1 - err = k8sClient.Status().Update(ctx, statefulSet) + av.Status.PreDeploymentEvaluationStatus = apicommon.StateSucceeded + err = k8sClient.Status().Update(ctx, av) Expect(err).To(BeNil()) By("Looking up the StatefulSet to retrieve its UID") @@ -183,10 +185,17 @@ var _ = Describe("KeptnWorkloadInstanceController", Ordered, func() { }, statefulSet) Expect(err).To(BeNil()) + By("Bringing the StatefulSet into its ready state") + statefulSet.Status.AvailableReplicas = 1 + statefulSet.Status.ReadyReplicas = 1 + statefulSet.Status.Replicas = 1 + err = k8sClient.Status().Update(ctx, statefulSet) + Expect(err).To(BeNil()) + By("Creating a WorkloadInstance that references the StatefulSet") wi = &klcv1alpha3.KeptnWorkloadInstance{ ObjectMeta: metav1.ObjectMeta{ - Name: name, + Name: appName, Namespace: namespace, }, Spec: klcv1alpha3.KeptnWorkloadInstanceSpec{ @@ -199,7 +208,7 @@ var _ = Describe("KeptnWorkloadInstanceController", Ordered, func() { Version: "2.0", AppName: appVersion.GetAppName(), }, - WorkloadName: "test-app-wname", + WorkloadName: appName + "-wname", TraceId: map[string]string{"traceparent": "00-0f89f15e562489e2e171eca1cf9ba958-d2fa6dbbcbf7e29a-01"}, }, } @@ -243,14 +252,16 @@ var _ = Describe("KeptnWorkloadInstanceController", Ordered, func() { Expect(err).To(BeNil()) By("Setting the App PreDeploymentEvaluation Status to 'Succeeded'") - appVersion.Status.PreDeploymentEvaluationStatus = apicommon.StateSucceeded - err = k8sClient.Status().Update(ctx, appVersion) + + av := &klcv1alpha3.KeptnAppVersion{} + err = k8sClient.Get(ctx, types.NamespacedName{ + Namespace: namespace, + Name: appName, + }, av) Expect(err).To(BeNil()) - By("Bringing the DaemonSet into its ready state") - daemonSet.Status.DesiredNumberScheduled = 1 - daemonSet.Status.NumberReady = 1 - err = k8sClient.Status().Update(ctx, daemonSet) + av.Status.PreDeploymentEvaluationStatus = apicommon.StateSucceeded + err = k8sClient.Status().Update(ctx, av) Expect(err).To(BeNil()) By("Looking up the DaemonSet to retrieve its UID") @@ -260,10 +271,16 @@ var _ = Describe("KeptnWorkloadInstanceController", Ordered, func() { }, daemonSet) Expect(err).To(BeNil()) + By("Bringing the DaemonSet into its ready state") + daemonSet.Status.DesiredNumberScheduled = 1 + daemonSet.Status.NumberReady = 1 + err = k8sClient.Status().Update(ctx, daemonSet) + Expect(err).To(BeNil()) + By("Creating a WorkloadInstance that references the DaemonSet") wi = &klcv1alpha3.KeptnWorkloadInstance{ ObjectMeta: metav1.ObjectMeta{ - Name: name, + Name: appName, Namespace: namespace, }, Spec: klcv1alpha3.KeptnWorkloadInstanceSpec{ @@ -276,7 +293,7 @@ var _ = Describe("KeptnWorkloadInstanceController", Ordered, func() { Version: "2.0", AppName: appVersion.GetAppName(), }, - WorkloadName: "test-app-wname", + WorkloadName: appName + "-wname", TraceId: map[string]string{"traceparent": "00-0f89f15e562489e2e171eca1cf9ba958-d2fa6dbbcbf7e29a-01"}, }, } @@ -303,7 +320,7 @@ var _ = Describe("KeptnWorkloadInstanceController", Ordered, func() { }, Spec: klcv1alpha3.KeptnEvaluationSpec{ EvaluationDefinition: "eval-def", - Workload: "test-app-wname", + Workload: appName + "-wname", WorkloadVersion: "2.0", Type: apicommon.PreDeploymentEvaluationCheckType, Retries: 10, @@ -318,6 +335,12 @@ var _ = Describe("KeptnWorkloadInstanceController", Ordered, func() { err := k8sClient.Create(context.TODO(), evaluation) Expect(err).To(BeNil()) + err = k8sClient.Get(ctx, types.NamespacedName{ + Namespace: namespace, + Name: evaluation.Name, + }, evaluation) + Expect(err).To(BeNil()) + evaluation.Status = klcv1alpha3.KeptnEvaluationStatus{ OverallStatus: apicommon.StateFailed, RetryCount: 10, @@ -336,7 +359,7 @@ var _ = Describe("KeptnWorkloadInstanceController", Ordered, func() { wi = &klcv1alpha3.KeptnWorkloadInstance{ ObjectMeta: metav1.ObjectMeta{ - Name: "test-app-wname-2.0", + Name: appName + "-wname-2.0", Namespace: namespace, }, Spec: klcv1alpha3.KeptnWorkloadInstanceSpec{ @@ -345,7 +368,7 @@ var _ = Describe("KeptnWorkloadInstanceController", Ordered, func() { AppName: appVersion.GetAppName(), PreDeploymentEvaluations: []string{"eval-def"}, }, - WorkloadName: "test-app-wname", + WorkloadName: appName + "-wname", }, } By("Creating WorkloadInstance") @@ -454,7 +477,15 @@ func createAppVersionInCluster(name string, namespace string, version string) *k By("Invoking Reconciling for Create") Expect(ignoreAlreadyExists(k8sClient.Create(ctx, instance))).Should(Succeed()) - instance.Status.PreDeploymentEvaluationStatus = apicommon.StateSucceeded - _ = k8sClient.Status().Update(ctx, instance) - return instance + + av := &klcv1alpha3.KeptnAppVersion{} + err := k8sClient.Get(ctx, types.NamespacedName{ + Namespace: namespace, + Name: name, + }, av) + Expect(err).To(BeNil()) + + av.Status.PreDeploymentEvaluationStatus = apicommon.StateSucceeded + _ = k8sClient.Status().Update(ctx, av) + return av } From 42e3cb545d6d619df3c825a820c9bbfb9c71fbb1 Mon Sep 17 00:00:00 2001 From: odubajDT Date: Wed, 8 Mar 2023 09:28:39 +0100 Subject: [PATCH 4/4] fix flakyness Signed-off-by: odubajDT --- operator/test/component/workloadinstancecontroller_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/operator/test/component/workloadinstancecontroller_test.go b/operator/test/component/workloadinstancecontroller_test.go index edb2b673ce..b474a631d0 100644 --- a/operator/test/component/workloadinstancecontroller_test.go +++ b/operator/test/component/workloadinstancecontroller_test.go @@ -92,7 +92,6 @@ var _ = Describe("KeptnWorkloadInstanceController", Ordered, func() { }) BeforeEach(func() { // list var here they will be copied for every spec - appName = names.SimpleNameGenerator.GenerateName("test-app-") namespace = "default" // namespaces are not deleted in the api so be careful // when creating you can use ignoreAlreadyExists(err error) version = "1.0.0" @@ -105,6 +104,7 @@ var _ = Describe("KeptnWorkloadInstanceController", Ordered, func() { Context("with a new AppVersions CRD", func() { BeforeEach(func() { + appName = names.SimpleNameGenerator.GenerateName("test-app-") appVersion = createAppVersionInCluster(appName, namespace, version) })