From dca473937057fbfdb66cb4030c98a3b0f391bd5b Mon Sep 17 00:00:00 2001 From: andoriyaprashant Date: Sat, 16 Aug 2025 04:22:01 +0530 Subject: [PATCH] add unit tests for core and worker controllers Signed-off-by: andoriyaprashant --- .../controller/cluster_controller_test.go | 142 ++++++++++++++++ .../serviceexportconfig_controller_test.go | 111 +++++++++++++ .../sliceqosconfig_controller_test.go | 111 +++++++++++++ controllers/worker/suite_test.go | 74 +++++++++ .../workerserviceimport_controller_test.go | 144 ++++++++++++++++ .../workersliceconfig_controller_test.go | 156 ++++++++++++++++++ .../workerslicegateway_controller_test.go | 127 ++++++++++++++ 7 files changed, 865 insertions(+) create mode 100644 controllers/controller/cluster_controller_test.go create mode 100644 controllers/controller/serviceexportconfig_controller_test.go create mode 100644 controllers/controller/sliceqosconfig_controller_test.go create mode 100644 controllers/worker/suite_test.go create mode 100644 controllers/worker/workerserviceimport_controller_test.go create mode 100644 controllers/worker/workersliceconfig_controller_test.go create mode 100644 controllers/worker/workerslicegateway_controller_test.go diff --git a/controllers/controller/cluster_controller_test.go b/controllers/controller/cluster_controller_test.go new file mode 100644 index 00000000..603e9efc --- /dev/null +++ b/controllers/controller/cluster_controller_test.go @@ -0,0 +1,142 @@ +package controller + +import ( + "context" + + "github.com/kubeslice/kubeslice-controller/apis/controller/v1alpha1" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + v1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" +) + +const ( + clusterName1 = "cluster-avesha" + clusterNamespace1 = "kubeslice-" + clusterName1 + clusterName2 = "cluster-demo" + clusterNamespace2 = "kubeslice-" + clusterName2 +) + +var _ = Describe("Cluster controller", func() { + When("Creating Cluster CR", func() { + It("should create Cluster CR and related resources without errors", func() { + By("Creating a new Cluster CR") + ctx := context.Background() + + cluster := &v1alpha1.Cluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: clusterName1, + Namespace: controlPlaneNamespace, + }, + Spec: v1alpha1.ClusterSpec{ + ClusterProperty: v1alpha1.ClusterProperty{ + Telemetry: v1alpha1.Telemetry{ + TelemetryProvider: "test-property", + }, + }, + }, + } + Expect(k8sClient.Create(ctx, cluster)).Should(Succeed()) + + By("Looking up the created Cluster CR") + clusterLookupKey := types.NamespacedName{ + Name: clusterName1, + Namespace: controlPlaneNamespace, + } + createdCluster := &v1alpha1.Cluster{} + Eventually(func() bool { + err := k8sClient.Get(ctx, clusterLookupKey, createdCluster) + return err == nil + }, timeout, interval).Should(BeTrue()) + + By("Looking up the created Cluster Namespace") + nsLookupKey := types.NamespacedName{ + Name: clusterNamespace1, + } + createdNS := &v1.Namespace{} + Eventually(func() bool { + err := k8sClient.Get(ctx, nsLookupKey, createdNS) + return err == nil + }, timeout, interval).Should(BeTrue()) + + By("Looking up the created Cluster Role") + roleLookupKey := types.NamespacedName{ + Name: "kubeslice-cluster-role", + Namespace: clusterNamespace1, + } + createdRole := &rbacv1.Role{} + Eventually(func() bool { + err := k8sClient.Get(ctx, roleLookupKey, createdRole) + return err == nil + }, timeout, interval).Should(BeTrue()) + + By("Looking up the created Cluster Role Binding") + rbLookupKey := types.NamespacedName{ + Name: "kubeslice-cluster-rolebinding", + Namespace: clusterNamespace1, + } + createdRB := &rbacv1.RoleBinding{} + Eventually(func() bool { + err := k8sClient.Get(ctx, rbLookupKey, createdRB) + return err == nil + }, timeout, interval).Should(BeTrue()) + + By("Looking up the created Cluster Service Account") + saLookupKey := types.NamespacedName{ + Name: "kubeslice-cluster-sa", + Namespace: clusterNamespace1, + } + createdSA := &v1.ServiceAccount{} + Eventually(func() bool { + err := k8sClient.Get(ctx, saLookupKey, createdSA) + return err == nil + }, timeout, interval).Should(BeTrue()) + + By("Deleting the created Cluster CR") + Expect(k8sClient.Delete(ctx, createdCluster)).Should(Succeed()) + Eventually(func() bool { + err := k8sClient.Get(ctx, clusterLookupKey, createdCluster) + return errors.IsNotFound(err) + }, timeout, interval).Should(BeTrue()) + }) + + It("should handle deletion of a Cluster CR gracefully", func() { + By("Creating a new Cluster CR") + ctx := context.Background() + + cluster := &v1alpha1.Cluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: clusterName2, + Namespace: controlPlaneNamespace, + }, + Spec: v1alpha1.ClusterSpec{ + ClusterProperty: v1alpha1.ClusterProperty{ + Telemetry: v1alpha1.Telemetry{ + TelemetryProvider: "test-property-2", + }, + }, + }, + } + Expect(k8sClient.Create(ctx, cluster)).Should(Succeed()) + + clusterLookupKey := types.NamespacedName{ + Name: clusterName2, + Namespace: controlPlaneNamespace, + } + + createdCluster := &v1alpha1.Cluster{} + + By("Deleting the created Cluster CR") + Expect(k8sClient.Delete(ctx, cluster)).Should(Succeed()) + + By("Looking up the deleted Cluster CR") + Eventually(func() bool { + err := k8sClient.Get(ctx, clusterLookupKey, createdCluster) + return err != nil + }, timeout, interval).Should(BeTrue()) + }) + }) +}) diff --git a/controllers/controller/serviceexportconfig_controller_test.go b/controllers/controller/serviceexportconfig_controller_test.go new file mode 100644 index 00000000..7be6e858 --- /dev/null +++ b/controllers/controller/serviceexportconfig_controller_test.go @@ -0,0 +1,111 @@ +package controller + +import ( + "context" + + controllerv1alpha1 "github.com/kubeslice/kubeslice-controller/apis/controller/v1alpha1" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" +) + +const ( + serviceExportConfigName1 = "sec-test-1" + serviceExportConfigName2 = "sec-test-2" +) + +var _ = Describe("ServiceExportConfig controller", func() { + When("Creating ServiceExportConfig CR", func() { + It("should create ServiceExportConfig CR without errors", func() { + By("Creating a new ServiceExportConfig CR") + ctx := context.Background() + + sec := &controllerv1alpha1.ServiceExportConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: serviceExportConfigName1, + Namespace: controlPlaneNamespace, + }, + Spec: controllerv1alpha1.ServiceExportConfigSpec{ + ServiceName: "test-service", + ServiceNamespace: "default", + SourceCluster: "test-cluster", + SliceName: "test-slice", + ServiceDiscoveryPorts: []controllerv1alpha1.ServiceDiscoveryPort{ + { + Name: "http", + Protocol: "TCP", + Port: 80, + ServicePort: 8080, + ServiceProtocol: "TCP", + }, + }, + }, + } + Expect(k8sClient.Create(ctx, sec)).Should(Succeed()) + + By("Looking up the created ServiceExportConfig CR") + secLookupKey := types.NamespacedName{ + Name: serviceExportConfigName1, + Namespace: controlPlaneNamespace, + } + createdSec := &controllerv1alpha1.ServiceExportConfig{} + Eventually(func() bool { + err := k8sClient.Get(ctx, secLookupKey, createdSec) + return err == nil + }, timeout, interval).Should(BeTrue()) + + By("Deleting the created ServiceExportConfig CR") + Expect(k8sClient.Delete(ctx, createdSec)).Should(Succeed()) + Eventually(func() bool { + err := k8sClient.Get(ctx, secLookupKey, createdSec) + return errors.IsNotFound(err) + }, timeout, interval).Should(BeTrue()) + }) + + It("should handle deletion of a ServiceExportConfig CR gracefully", func() { + By("Creating a new ServiceExportConfig CR") + ctx := context.Background() + + sec := &controllerv1alpha1.ServiceExportConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: serviceExportConfigName2, + Namespace: controlPlaneNamespace, + }, + Spec: controllerv1alpha1.ServiceExportConfigSpec{ + ServiceName: "another-service", + ServiceNamespace: "default", + SourceCluster: "test-cluster-2", + SliceName: "another-slice", + ServiceDiscoveryPorts: []controllerv1alpha1.ServiceDiscoveryPort{ + { + Name: "https", + Protocol: "TCP", + Port: 443, + ServicePort: 8443, + ServiceProtocol: "TCP", + }, + }, + }, + } + Expect(k8sClient.Create(ctx, sec)).Should(Succeed()) + + secLookupKey := types.NamespacedName{ + Name: serviceExportConfigName2, + Namespace: controlPlaneNamespace, + } + + createdSec := &controllerv1alpha1.ServiceExportConfig{} + + By("Deleting the created ServiceExportConfig CR") + Expect(k8sClient.Delete(ctx, sec)).Should(Succeed()) + + By("Looking up the deleted ServiceExportConfig CR") + Eventually(func() bool { + err := k8sClient.Get(ctx, secLookupKey, createdSec) + return errors.IsNotFound(err) + }, timeout, interval).Should(BeTrue()) + }) + }) +}) diff --git a/controllers/controller/sliceqosconfig_controller_test.go b/controllers/controller/sliceqosconfig_controller_test.go new file mode 100644 index 00000000..67cf5014 --- /dev/null +++ b/controllers/controller/sliceqosconfig_controller_test.go @@ -0,0 +1,111 @@ +package controller + +import ( + "context" + + controllerv1alpha1 "github.com/kubeslice/kubeslice-controller/apis/controller/v1alpha1" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" +) + +const ( + sliceQoSConfigName1 = "sliceqos-test-1" + sliceQoSConfigName2 = "sliceqos-test-2" +) + +var _ = Describe("SliceQoSConfig controller", func() { + When("Creating SliceQoSConfig CR", func() { + It("should create and delete SliceQoSConfig CR without errors", func() { + By("Creating a new SliceQoSConfig CR") + ctx := context.Background() + + expectedSpec := controllerv1alpha1.SliceQoSConfigSpec{ + QueueType: "HTB", + Priority: 1, + TcType: "BANDWIDTH_CONTROL", + BandwidthCeilingKbps: 100000, + BandwidthGuaranteedKbps: 50000, + DscpClass: "AF11", + } + + sliceQoS := &controllerv1alpha1.SliceQoSConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: sliceQoSConfigName1, + Namespace: controlPlaneNamespace, + }, + Spec: expectedSpec, + } + Expect(k8sClient.Create(ctx, sliceQoS)).Should(Succeed()) + + By("Looking up the created SliceQoSConfig CR") + sliceQoSLookupKey := types.NamespacedName{ + Name: sliceQoSConfigName1, + Namespace: controlPlaneNamespace, + } + createdSliceQoS := &controllerv1alpha1.SliceQoSConfig{} + Eventually(func() bool { + err := k8sClient.Get(ctx, sliceQoSLookupKey, createdSliceQoS) + return err == nil + }, timeout, interval).Should(BeTrue()) + + By("Verifying the created CR has the expected spec values") + Expect(createdSliceQoS.Spec).To(Equal(expectedSpec)) + + By("Deleting the created SliceQoSConfig CR") + Expect(k8sClient.Delete(ctx, createdSliceQoS)).Should(Succeed()) + Eventually(func() bool { + err := k8sClient.Get(ctx, sliceQoSLookupKey, createdSliceQoS) + return errors.IsNotFound(err) + }, timeout, interval).Should(BeTrue()) + }) + + It("should handle deletion of a SliceQoSConfig CR gracefully", func() { + By("Creating a new SliceQoSConfig CR") + ctx := context.Background() + + expectedSpec := controllerv1alpha1.SliceQoSConfigSpec{ + QueueType: "HTB", + Priority: 2, + TcType: "BANDWIDTH_CONTROL", + BandwidthCeilingKbps: 200000, + BandwidthGuaranteedKbps: 100000, + DscpClass: "AF21", + } + + sliceQoS := &controllerv1alpha1.SliceQoSConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: sliceQoSConfigName2, + Namespace: controlPlaneNamespace, + }, + Spec: expectedSpec, + } + Expect(k8sClient.Create(ctx, sliceQoS)).Should(Succeed()) + + sliceQoSLookupKey := types.NamespacedName{ + Name: sliceQoSConfigName2, + Namespace: controlPlaneNamespace, + } + + createdSliceQoS := &controllerv1alpha1.SliceQoSConfig{} + + By("Looking up the created CR and verifying spec values") + Eventually(func() bool { + err := k8sClient.Get(ctx, sliceQoSLookupKey, createdSliceQoS) + return err == nil + }, timeout, interval).Should(BeTrue()) + Expect(createdSliceQoS.Spec).To(Equal(expectedSpec)) + + By("Deleting the created SliceQoSConfig CR") + Expect(k8sClient.Delete(ctx, sliceQoS)).Should(Succeed()) + + By("Ensuring the CR is actually deleted") + Eventually(func() bool { + err := k8sClient.Get(ctx, sliceQoSLookupKey, createdSliceQoS) + return errors.IsNotFound(err) + }, timeout, interval).Should(BeTrue()) + }) + }) +}) diff --git a/controllers/worker/suite_test.go b/controllers/worker/suite_test.go new file mode 100644 index 00000000..4e84334f --- /dev/null +++ b/controllers/worker/suite_test.go @@ -0,0 +1,74 @@ +package worker + +import ( + "context" + "path/filepath" + "testing" + "time" + + workerv1alpha1 "github.com/kubeslice/kubeslice-controller/apis/worker/v1alpha1" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/envtest" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" +) + +var ( + cfg *rest.Config + k8sClient client.Client + testEnv *envtest.Environment + ctx context.Context + cancel context.CancelFunc + controlPlaneNamespace = "kubeslice-system" + + // Added these so Ginkgo tests can use them + timeout time.Duration + interval time.Duration +) + +func TestAPIs(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Worker Controller Suite") +} + +var _ = BeforeSuite(func() { + logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) + + ctx, cancel = context.WithCancel(context.TODO()) + + By("bootstrapping test environment") + testEnv = &envtest.Environment{ + CRDDirectoryPaths: []string{ + filepath.Join("..", "..", "config", "crd", "bases"), + }, + ErrorIfCRDPathMissing: false, + } + + var err error + cfg, err = testEnv.Start() + Expect(err).NotTo(HaveOccurred()) + Expect(cfg).NotTo(BeNil()) + + // register the worker API scheme + err = workerv1alpha1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + + k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) + Expect(err).NotTo(HaveOccurred()) + Expect(k8sClient).NotTo(BeNil()) + + // initialize timeout and interval values here + timeout = time.Second * 10 + interval = time.Millisecond * 250 +}) + +var _ = AfterSuite(func() { + By("tearing down the test environment") + cancel() + err := testEnv.Stop() + Expect(err).NotTo(HaveOccurred()) +}) diff --git a/controllers/worker/workerserviceimport_controller_test.go b/controllers/worker/workerserviceimport_controller_test.go new file mode 100644 index 00000000..a1462401 --- /dev/null +++ b/controllers/worker/workerserviceimport_controller_test.go @@ -0,0 +1,144 @@ +package worker + +import ( + "context" + + workerv1alpha1 "github.com/kubeslice/kubeslice-controller/apis/worker/v1alpha1" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" +) + +const ( + workerServiceImportName1 = "workerserviceimport-test-1" + workerServiceImportName2 = "workerserviceimport-test-2" +) + +var _ = Describe("WorkerServiceImport controller", func() { + When("Creating WorkerServiceImport CR", func() { + It("should create and delete WorkerServiceImport CR without errors", func() { + By("Creating a new WorkerServiceImport CR") + ctx := context.Background() + + expectedSpec := workerv1alpha1.WorkerServiceImportSpec{ + ServiceName: "test-service", + ServiceNamespace: "default", + SourceClusters: []string{"cluster-a", "cluster-b"}, + SliceName: "test-slice", + ServiceDiscoveryEndpoints: []workerv1alpha1.ServiceDiscoveryEndpoint{ + { + PodName: "pod-1", + Cluster: "cluster-a", + NsmIp: "10.0.0.1", + DnsName: "service.slice.local", + Port: 8080, + }, + }, + ServiceDiscoveryPorts: []workerv1alpha1.ServiceDiscoveryPort{ + { + Name: "http", + Port: 8080, + Protocol: "TCP", + ServicePort: 80, + ServiceProtocol: "TCP", + }, + }, + Aliases: []string{"alias1", "alias2"}, + } + + workerServiceImport := &workerv1alpha1.WorkerServiceImport{ + ObjectMeta: metav1.ObjectMeta{ + Name: workerServiceImportName1, + Namespace: controlPlaneNamespace, + }, + Spec: expectedSpec, + } + Expect(k8sClient.Create(ctx, workerServiceImport)).Should(Succeed()) + + By("Looking up the created WorkerServiceImport CR") + lookupKey := types.NamespacedName{ + Name: workerServiceImportName1, + Namespace: controlPlaneNamespace, + } + createdObj := &workerv1alpha1.WorkerServiceImport{} + Eventually(func() bool { + err := k8sClient.Get(ctx, lookupKey, createdObj) + return err == nil + }, timeout, interval).Should(BeTrue()) + + By("Verifying the created CR has the expected spec values") + Expect(createdObj.Spec).To(Equal(expectedSpec)) + + By("Deleting the created WorkerServiceImport CR") + Expect(k8sClient.Delete(ctx, createdObj)).Should(Succeed()) + Eventually(func() bool { + err := k8sClient.Get(ctx, lookupKey, createdObj) + return errors.IsNotFound(err) + }, timeout, interval).Should(BeTrue()) + }) + + It("should handle deletion of a WorkerServiceImport CR gracefully", func() { + By("Creating a new WorkerServiceImport CR") + ctx := context.Background() + + expectedSpec := workerv1alpha1.WorkerServiceImportSpec{ + ServiceName: "test-service-2", + ServiceNamespace: "default", + SourceClusters: []string{"cluster-x"}, + SliceName: "slice-2", + ServiceDiscoveryEndpoints: []workerv1alpha1.ServiceDiscoveryEndpoint{ + { + PodName: "pod-2", + Cluster: "cluster-x", + NsmIp: "10.0.0.2", + DnsName: "svc2.slice.local", + Port: 9090, + }, + }, + ServiceDiscoveryPorts: []workerv1alpha1.ServiceDiscoveryPort{ + { + Name: "grpc", + Port: 9090, + Protocol: "TCP", + ServicePort: 90, + ServiceProtocol: "TCP", + }, + }, + Aliases: []string{"alias-x"}, + } + + workerServiceImport := &workerv1alpha1.WorkerServiceImport{ + ObjectMeta: metav1.ObjectMeta{ + Name: workerServiceImportName2, + Namespace: controlPlaneNamespace, + }, + Spec: expectedSpec, + } + Expect(k8sClient.Create(ctx, workerServiceImport)).Should(Succeed()) + + lookupKey := types.NamespacedName{ + Name: workerServiceImportName2, + Namespace: controlPlaneNamespace, + } + createdObj := &workerv1alpha1.WorkerServiceImport{} + + By("Looking up the created CR and verifying spec values") + Eventually(func() bool { + err := k8sClient.Get(ctx, lookupKey, createdObj) + return err == nil + }, timeout, interval).Should(BeTrue()) + Expect(createdObj.Spec).To(Equal(expectedSpec)) + + By("Deleting the created WorkerServiceImport CR") + Expect(k8sClient.Delete(ctx, workerServiceImport)).Should(Succeed()) + + By("Ensuring the CR is actually deleted") + Eventually(func() bool { + err := k8sClient.Get(ctx, lookupKey, createdObj) + return errors.IsNotFound(err) + }, timeout, interval).Should(BeTrue()) + }) + }) +}) diff --git a/controllers/worker/workersliceconfig_controller_test.go b/controllers/worker/workersliceconfig_controller_test.go new file mode 100644 index 00000000..1107cf5e --- /dev/null +++ b/controllers/worker/workersliceconfig_controller_test.go @@ -0,0 +1,156 @@ +package worker + +import ( + "context" + "time" + + controllerv1alpha1 "github.com/kubeslice/kubeslice-controller/apis/controller/v1alpha1" + workerv1alpha1 "github.com/kubeslice/kubeslice-controller/apis/worker/v1alpha1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes/scheme" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + ctrl "sigs.k8s.io/controller-runtime" +) + +type fakeWorkerSliceService struct{} + +func (f *fakeWorkerSliceService) ReconcileWorkerSliceConfig(_ context.Context, _ ctrl.Request) (ctrl.Result, error) { + return ctrl.Result{}, nil +} + +func (f *fakeWorkerSliceService) ComputeClusterMap(_ []string, _ []workerv1alpha1.WorkerSliceConfig) map[string]int { + return map[string]int{ + "cluster-1": 1, + "cluster-2": 2, + } +} + +func (f *fakeWorkerSliceService) DeleteWorkerSliceConfigByLabel(_ context.Context, _ map[string]string, _ string) error { + return nil +} + +func (f *fakeWorkerSliceService) ListWorkerSliceConfigs(_ context.Context, _ map[string]string, _ string) ([]workerv1alpha1.WorkerSliceConfig, error) { + return []workerv1alpha1.WorkerSliceConfig{}, nil +} + +func (f *fakeWorkerSliceService) CreateMinimalWorkerSliceConfig( + _ context.Context, + clusters []string, + _ string, + _ map[string]string, + _ string, + _ string, + _ string, + _ map[string]*controllerv1alpha1.SliceGatewayServiceType, +) (map[string]int, error) { + return map[string]int{"cluster-1": len(clusters)}, nil +} + +func (f *fakeWorkerSliceService) CreateMinimalWorkerSliceConfigForNoNetworkSlice( + _ context.Context, + _ []string, + _ string, + _ map[string]string, + _ string, +) error { + return nil +} + +var _ = Describe("WorkerSliceConfig Controller", func() { + const ( + WorkerSliceConfigName = "test-worker-slice-config" + WorkerSliceConfigNS = "default" + timeout = time.Second * 10 + interval = time.Millisecond * 250 + ) + + Context("When creating a WorkerSliceConfig", func() { + It("Should successfully reconcile the WorkerSliceConfig resource", func() { + By("Creating the WorkerSliceConfig resource") + ctx := context.Background() + + workerSliceConfig := &workerv1alpha1.WorkerSliceConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: WorkerSliceConfigName, + Namespace: WorkerSliceConfigNS, + }, + Spec: workerv1alpha1.WorkerSliceConfigSpec{ + SliceName: "test-slice", + SliceSubnet: "10.1.0.0/16", + SliceType: "Application", + SliceGatewayProvider: workerv1alpha1.WorkerSliceGatewayProvider{ + SliceGatewayType: "OpenVPN", + SliceCaType: "Local", + SliceGatewayServiceType: "NodePort", + SliceGatewayProtocol: "UDP", + }, + SliceIpamType: "Local", + QosProfileDetails: workerv1alpha1.QOSProfile{ + QueueType: "HTB", + Priority: 1, + TcType: "tbf", + BandwidthCeilingKbps: 10000, + BandwidthGuaranteedKbps: 5000, + DscpClass: "AF21", + }, + NamespaceIsolationProfile: workerv1alpha1.NamespaceIsolationProfile{ + IsolationEnabled: false, + ApplicationNamespaces: []string{"app-ns"}, + AllowedNamespaces: []string{"allowed-ns"}, + }, + IpamClusterOctet: 10, + ClusterSubnetCIDR: "192.168.0.0/16", + ExternalGatewayConfig: workerv1alpha1.ExternalGatewayConfig{ + Ingress: workerv1alpha1.ExternalGatewayConfigOptions{ + Enabled: true, + }, + Egress: workerv1alpha1.ExternalGatewayConfigOptions{ + Enabled: false, + }, + }, + }, + } + + Expect(k8sClient.Create(ctx, workerSliceConfig)).Should(Succeed()) + + By("Ensuring the WorkerSliceConfig resource can be retrieved") + fetched := &workerv1alpha1.WorkerSliceConfig{} + Eventually(func() bool { + err := k8sClient.Get(ctx, types.NamespacedName{ + Name: WorkerSliceConfigName, + Namespace: WorkerSliceConfigNS, + }, fetched) + return err == nil + }, timeout, interval).Should(BeTrue()) + + Expect(fetched.Spec.SliceName).To(Equal("test-slice")) + Expect(fetched.Spec.SliceSubnet).To(Equal("10.1.0.0/16")) + }) + }) + + Context("When reconciling an existing WorkerSliceConfig", func() { + It("Should trigger the reconcile logic without errors", func() { + ctx := context.Background() + reconciler := &WorkerSliceConfigReconciler{ + Client: k8sClient, + Scheme: scheme.Scheme, + WorkerSliceService: &fakeWorkerSliceService{}, + Log: nil, + EventRecorder: nil, + } + + req := ctrl.Request{ + NamespacedName: types.NamespacedName{ + Name: WorkerSliceConfigName, + Namespace: WorkerSliceConfigNS, + }, + } + + _, err := reconciler.Reconcile(ctx, req) + Expect(err).NotTo(HaveOccurred()) + }) + }) +}) diff --git a/controllers/worker/workerslicegateway_controller_test.go b/controllers/worker/workerslicegateway_controller_test.go new file mode 100644 index 00000000..fe58549a --- /dev/null +++ b/controllers/worker/workerslicegateway_controller_test.go @@ -0,0 +1,127 @@ +package worker + +import ( + "context" + + workerv1alpha1 "github.com/kubeslice/kubeslice-controller/apis/worker/v1alpha1" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" +) + +const ( + wsGatewayName1 = "test-wsgateway" + wsGatewayNamespace1 = "default" + wsGatewayName2 = "test-wsgateway-delete" + wsGatewayNamespace2 = "default" +) + +var _ = Describe("WorkerSliceGateway Controller", func() { + When("Creating WorkerSliceGateway CR", func() { + It("should create the CR and reconcile without errors", func() { + By("Creating a new WorkerSliceGateway CR") + ctx := context.Background() + + workerSliceGateway := &workerv1alpha1.WorkerSliceGateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: wsGatewayName1, + Namespace: wsGatewayNamespace1, + }, + Spec: workerv1alpha1.WorkerSliceGatewaySpec{ + SliceName: "test-slice", + GatewayType: "OpenVPN", + GatewayHostType: "Server", + GatewayConnectivityType: "NodePort", + GatewayProtocol: "UDP", + GatewayCredentials: workerv1alpha1.GatewayCredentials{ + SecretName: "test-secret", + }, + LocalGatewayConfig: workerv1alpha1.SliceGatewayConfig{ + NodeIps: []string{"10.0.0.1"}, + NodePort: 30001, + GatewayName: "local-gateway", + ClusterName: "local-cluster", + VpnIp: "192.168.1.1", + GatewaySubnet: "192.168.1.0/24", + }, + RemoteGatewayConfig: workerv1alpha1.SliceGatewayConfig{ + NodeIps: []string{"10.0.0.2"}, + NodePort: 30002, + GatewayName: "remote-gateway", + ClusterName: "remote-cluster", + VpnIp: "192.168.2.1", + GatewaySubnet: "192.168.2.0/24", + }, + GatewayNumber: 1, + }, + } + + Expect(k8sClient.Create(ctx, workerSliceGateway)).Should(Succeed()) + + By("Looking up the created WorkerSliceGateway CR") + wsLookupKey := types.NamespacedName{ + Name: wsGatewayName1, + Namespace: wsGatewayNamespace1, + } + createdWSG := &workerv1alpha1.WorkerSliceGateway{} + Eventually(func() bool { + err := k8sClient.Get(ctx, wsLookupKey, createdWSG) + return err == nil + }, timeout, interval).Should(BeTrue()) + + By("Verifying the spec fields match what was set") + Expect(createdWSG.Spec.SliceName).To(Equal("test-slice")) + Expect(createdWSG.Spec.LocalGatewayConfig.GatewayName).To(Equal("local-gateway")) + Expect(createdWSG.Spec.RemoteGatewayConfig.GatewayName).To(Equal("remote-gateway")) + Expect(createdWSG.Spec.GatewayNumber).To(Equal(1)) + + By("Deleting the created WorkerSliceGateway CR") + Expect(k8sClient.Delete(ctx, createdWSG)).Should(Succeed()) + + By("Verifying the CR is deleted") + Eventually(func() bool { + err := k8sClient.Get(ctx, wsLookupKey, createdWSG) + return errors.IsNotFound(err) + }, timeout, interval).Should(BeTrue()) + }) + + It("should delete WorkerSliceGateway CR without errors", func() { + By("Creating a new WorkerSliceGateway CR for deletion test") + ctx := context.Background() + + workerSliceGateway := &workerv1alpha1.WorkerSliceGateway{ + ObjectMeta: metav1.ObjectMeta{ + Name: wsGatewayName2, + Namespace: wsGatewayNamespace2, + }, + Spec: workerv1alpha1.WorkerSliceGatewaySpec{ + SliceName: "delete-slice", + GatewayType: "OpenVPN", + GatewayHostType: "Client", + GatewayConnectivityType: "NodePort", + GatewayProtocol: "TCP", + GatewayNumber: 2, + }, + } + + Expect(k8sClient.Create(ctx, workerSliceGateway)).Should(Succeed()) + + wsLookupKey := types.NamespacedName{ + Name: wsGatewayName2, + Namespace: wsGatewayNamespace2, + } + createdWSG := &workerv1alpha1.WorkerSliceGateway{} + + By("Deleting the created WorkerSliceGateway CR") + Expect(k8sClient.Delete(ctx, workerSliceGateway)).Should(Succeed()) + + By("Verifying the WorkerSliceGateway CR is deleted") + Eventually(func() bool { + err := k8sClient.Get(ctx, wsLookupKey, createdWSG) + return errors.IsNotFound(err) + }, timeout, interval).Should(BeTrue()) + }) + }) +})