diff --git a/controllers/route_test.go b/controllers/route_test.go new file mode 100644 index 00000000..60313b43 --- /dev/null +++ b/controllers/route_test.go @@ -0,0 +1,71 @@ +package controllers + +import ( + "context" + "github.com/google/uuid" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + routev1 "github.com/openshift/api/route/v1" + trustyaiopendatahubiov1alpha1 "github.com/trustyai-explainability/trustyai-service-operator/api/v1alpha1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes/scheme" + "time" +) + +var _ = Describe("Route Reconciliation", func() { + + BeforeEach(func() { + reconciler = &TrustyAIServiceReconciler{ + Client: k8sClient, + Scheme: scheme.Scheme, + EventRecorder: recorder, + } + ctx = context.Background() + }) + + Context("When Route does not exist", func() { + var instance *trustyaiopendatahubiov1alpha1.TrustyAIService + It("Should create Route successfully", func() { + namespace := "route-test-namespace-1" + instance = createDefaultCR(namespace) + instance.ObjectMeta.UID = types.UID(uuid.New().String()) // Set a UID for the TrustyAIService + Eventually(func() error { + return createNamespace(ctx, k8sClient, namespace) + }, time.Second*10, time.Millisecond*250).Should(Succeed(), "failed to create namespace") + + err := reconciler.reconcileRoute(instance, ctx) + Expect(err).ToNot(HaveOccurred()) + + route := &routev1.Route{} + err = reconciler.Client.Get(ctx, types.NamespacedName{Name: instance.Name, Namespace: instance.Namespace}, route) + Expect(err).ToNot(HaveOccurred()) + Expect(route).ToNot(BeNil()) + Expect(route.Spec.To.Name).To(Equal(instance.Name)) + }) + }) + + Context("When Route exists and is the same", func() { + var instance *trustyaiopendatahubiov1alpha1.TrustyAIService + It("Should not update Route", func() { + namespace := "route-test-namespace-2" + instance = createDefaultCR(namespace) + instance.ObjectMeta.UID = types.UID(uuid.New().String()) // Set a UID for the TrustyAIService + Eventually(func() error { + return createNamespace(ctx, k8sClient, namespace) + }, time.Second*10, time.Millisecond*250).Should(Succeed(), "failed to create namespace") + + // Create a Route with the expected spec + existingRoute, _ := reconciler.createRouteObject(instance) + Expect(reconciler.Client.Create(ctx, existingRoute)).To(Succeed()) + + err := reconciler.reconcileRoute(instance, ctx) + Expect(err).ToNot(HaveOccurred()) + + // Fetch the Route + route := &routev1.Route{} + err = reconciler.Client.Get(ctx, types.NamespacedName{Name: instance.Name, Namespace: instance.Namespace}, route) + Expect(err).ToNot(HaveOccurred()) + Expect(route.Spec).To(Equal(existingRoute.Spec)) + }) + }) +}) diff --git a/controllers/storage_test.go b/controllers/storage_test.go new file mode 100644 index 00000000..f026f7a5 --- /dev/null +++ b/controllers/storage_test.go @@ -0,0 +1,81 @@ +package controllers + +import ( + "context" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + trustyaiopendatahubiov1alpha1 "github.com/trustyai-explainability/trustyai-service-operator/api/v1alpha1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/tools/record" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "time" +) + +var _ = Describe("PVC Reconciliation", func() { + + BeforeEach(func() { + k8sClient = fake.NewClientBuilder().WithScheme(scheme.Scheme).Build() + recorder = record.NewFakeRecorder(10) + reconciler = &TrustyAIServiceReconciler{ + Client: k8sClient, + Scheme: scheme.Scheme, + EventRecorder: recorder, + } + ctx = context.Background() + }) + + Context("when the PVC does not exist", func() { + var instance *trustyaiopendatahubiov1alpha1.TrustyAIService + It("should create a new PVC and emit an event", func() { + namespace := "pvc-test-namespace-1" + instance = createDefaultCR(namespace) + Eventually(func() error { + return createNamespace(ctx, k8sClient, namespace) + }, time.Second*10, time.Millisecond*250).Should(Succeed(), "failed to create namespace") + + err := reconciler.ensurePVC(ctx, instance) + Expect(err).ToNot(HaveOccurred()) + + pvc := &corev1.PersistentVolumeClaim{} + err = k8sClient.Get(ctx, types.NamespacedName{Name: generatePVCName(instance), Namespace: instance.Namespace}, pvc) + Expect(err).ToNot(HaveOccurred()) + Expect(pvc).ToNot(BeNil()) + Expect(pvc.Spec.Resources.Requests[corev1.ResourceStorage]).To(Equal(resource.MustParse(instance.Spec.Storage.Size))) + + // Check for event + Eventually(recorder.Events).Should(Receive(ContainSubstring("Created PVC"))) + }) + }) + + Context("when the PVC already exists", func() { + var instance *trustyaiopendatahubiov1alpha1.TrustyAIService + It("should not attempt to create the PVC", func() { + namespace := "pvc-test-namespace-2" + instance = createDefaultCR(namespace) + Eventually(func() error { + return createNamespace(ctx, k8sClient, namespace) + }, time.Second*10, time.Millisecond*250).Should(Succeed(), "failed to create namespace") + + // Simulate existing PVC + existingPVC := &corev1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: generatePVCName(instance), + Namespace: instance.Namespace, + }, + } + err := k8sClient.Create(ctx, existingPVC) + Expect(err).ToNot(HaveOccurred()) + + err = reconciler.ensurePVC(ctx, instance) + Expect(err).ToNot(HaveOccurred()) + + // Check no event related for PVC creation + Consistently(recorder.Events).ShouldNot(Receive(ContainSubstring("Created PVC"))) + }) + }) + +}) diff --git a/controllers/suite_test.go b/controllers/suite_test.go index 4d9cd43a..4d96e9d0 100644 --- a/controllers/suite_test.go +++ b/controllers/suite_test.go @@ -34,6 +34,7 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" + "k8s.io/client-go/tools/record" "path/filepath" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" @@ -48,11 +49,16 @@ import ( // These tests use Ginkgo (BDD-style Go testing framework). Refer to // http://onsi.github.io/ginkgo/ to learn more about Ginkgo. -var cfg *rest.Config -var k8sClient client.Client -var testEnv *envtest.Environment -var ctx context.Context -var cancel context.CancelFunc +var ( + cfg *rest.Config + k8sClient client.Client + testEnv *envtest.Environment + ctx context.Context + cancel context.CancelFunc + reconciler *TrustyAIServiceReconciler + instance *trustyaiopendatahubiov1alpha1.TrustyAIService + recorder *record.FakeRecorder +) const ( name = "example-trustyai-service" @@ -270,8 +276,7 @@ var _ = BeforeSuite(func() { recorder := k8sManager.GetEventRecorderFor("trustyai-service-operator") err = (&TrustyAIServiceReconciler{ - Client: k8sManager.GetClient(), - //Log: ctrl.Log.WithName("controllers").WithName("YourController"), + Client: k8sManager.GetClient(), Scheme: k8sManager.GetScheme(), EventRecorder: recorder, }).SetupWithManager(k8sManager) @@ -431,7 +436,7 @@ var _ = Describe("TrustyAI operator", func() { Context("Testing deployment with defaults in multiple namespaces", func() { var instances []*trustyaiopendatahubiov1alpha1.TrustyAIService - namespaces := []string{"namespace1", "namespace2", "namespace3"} + var namespaces = []string{"namespace1", "namespace2", "namespace3"} BeforeEach(func() { instances = make([]*trustyaiopendatahubiov1alpha1.TrustyAIService, len(namespaces)) @@ -445,7 +450,6 @@ var _ = Describe("TrustyAI operator", func() { }) It("should deploy the services with defaults", func() { - ctx = context.Background() for _, instance := range instances { Expect(k8sClient.Create(ctx, instance)).Should(Succeed()) deployment := &appsv1.Deployment{} diff --git a/go.mod b/go.mod index 8a547dd5..15fd2572 100644 --- a/go.mod +++ b/go.mod @@ -26,6 +26,7 @@ require ( github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/emicklei/go-restful/v3 v3.10.2 // indirect + github.com/evanphx/json-patch v5.6.0+incompatible // indirect github.com/evanphx/json-patch/v5 v5.6.0 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/go-logr/logr v1.2.4 // indirect diff --git a/go.sum b/go.sum index bd49cdc8..272554d7 100644 --- a/go.sum +++ b/go.sum @@ -74,6 +74,7 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7 github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= +github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0= @@ -495,8 +496,6 @@ google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= -google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag= -google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8= google.golang.org/grpc v1.56.3 h1:8I4C0Yq1EjstUzUJzpcRVbuYA2mODtEmpWiQoN/b2nc= google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=