Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,16 @@ fmt: ## Run go fmt against code.
vet: ## Run go vet against code.
go vet ./...

.PHONY: e2e-test
e2e-test: ## Run end-to-end tests located in the ./e2e directory.
@echo " Running E2E tests..."
@if ! command -v ginkgo &> /dev/null; then \
echo " Installing Ginkgo..."; \
go install github.com/onsi/ginkgo/v2/ginkgo@latest; \
fi
@echo " Using Ginkgo binary at: $$(go env GOPATH)/bin/ginkgo"
$$(go env GOPATH)/bin/ginkgo run --v --randomize-all --fail-fast --timeout=15m ./e2e

.PHONY: test
test: fmt vet envtest ## Run tests.
KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) -p path)" go test -v -coverprofile=coverage.out -coverpkg ./... ./...
Expand Down
69 changes: 69 additions & 0 deletions e2e/serviceexport_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package e2e

import (
"context"
"time"

kubeslicev1beta1 "github.com/kubeslice/worker-operator/api/v1beta1"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
)

var _ = Describe("ServiceExport E2E", func() {
var (
ctx context.Context
namespace string
sliceName string
)

BeforeEach(func() {
ctx = context.Background()
namespace = "test-namespace"
sliceName = "e2e-slice"

// Create namespace if not exists
createNamespaceIfNotExists(ctx, namespace)

})

It("should create a ServiceExport and update status correctly", func() {
// Deploy app pod matching ServiceExport selector
labels := map[string]string{"app": "test-app"}
createTestPod(ctx, namespace, "app-pod", labels)

// Create slice first (required for ServiceExport to reconcile)
createSlice(ctx, sliceName)

// Create ServiceExport object
se := &kubeslicev1beta1.ServiceExport{
ObjectMeta: metav1.ObjectMeta{
Name: "test-svc-export",
Namespace: namespace,
},
Spec: kubeslicev1beta1.ServiceExportSpec{
Selector: &metav1.LabelSelector{
MatchLabels: labels,
},
Slice: sliceName,
Ports: []kubeslicev1beta1.ServicePort{
{
Name: "http",
Protocol: "TCP",
},
},
},
}

Expect(k8sClient.Create(ctx, se)).To(Succeed())

Eventually(func(g Gomega) {
updated := &kubeslicev1beta1.ServiceExport{}
g.Expect(k8sClient.Get(ctx, client.ObjectKey{Name: se.Name, Namespace: namespace}, updated)).To(Succeed())
g.Expect(updated.Status.ExportStatus).To(Equal(kubeslicev1beta1.ExportStatusReady))
g.Expect(updated.Status.AvailableEndpoints).To(BeNumerically(">", 0))
}).WithTimeout(2 * time.Minute).WithPolling(2 * time.Second).Should(Succeed())
})

})
79 changes: 79 additions & 0 deletions e2e/serviceimport_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package e2e

import (
"context"
"time"

kubeslicev1beta1 "github.com/kubeslice/worker-operator/api/v1beta1"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"
)

var _ = Describe("ServiceImport E2E", func() {
var (
ctx context.Context
namespace string
sliceName string
importName string
)

BeforeEach(func() {
ctx = context.Background()
namespace = "test-namespace"
// sliceName = "test-slice"
sliceName = "e2e-slice"
importName = "test-serviceimport"

createNamespaceIfNotExists(ctx, namespace)

// createSlice(ctx, sliceName)
})

It("should create a ServiceImport and reconcile status correctly", func() {
// Deploy a test pod that will act as an endpoint
labels := map[string]string{"app": "test-app"}
createTestPod(ctx, namespace, "test-pod", labels)

svcImport := &kubeslicev1beta1.ServiceImport{
ObjectMeta: metav1.ObjectMeta{
Name: importName,
Namespace: namespace,
},
Spec: kubeslicev1beta1.ServiceImportSpec{
Slice: sliceName,
DNSName: "test-app.slice.local",
Ports: []kubeslicev1beta1.ServicePort{
{
Name: "http",
ContainerPort: 8080,
Protocol: corev1.ProtocolTCP,
},
},
Aliases: []string{"alias-test-app"},
},
}

Expect(k8sClient.Create(ctx, svcImport)).To(Succeed())

Eventually(func(g Gomega) {
updated := &kubeslicev1beta1.ServiceImport{}
g.Expect(k8sClient.Get(ctx, client.ObjectKey{Name: importName, Namespace: namespace}, updated)).To(Succeed())
g.Expect(updated.Status.ImportStatus).To(Equal(kubeslicev1beta1.ImportStatusReady))
}).WithTimeout(2 * time.Minute).WithPolling(2 * time.Second).Should(Succeed())

// if ExposedPorts field is populated
Eventually(func(g Gomega) {
updated := &kubeslicev1beta1.ServiceImport{}
g.Expect(k8sClient.Get(ctx, client.ObjectKey{Name: importName, Namespace: namespace}, updated)).To(Succeed())
g.Expect(updated.Status.ExposedPorts).NotTo(BeEmpty())
}).WithTimeout(2 * time.Minute).WithPolling(2 * time.Second).Should(Succeed())

// Check AvailableEndpoints count (should be >= 0 — may depend on controller behavior)
updated := &kubeslicev1beta1.ServiceImport{}
Expect(k8sClient.Get(ctx, client.ObjectKey{Name: importName, Namespace: namespace}, updated)).To(Succeed())
Expect(updated.Status.AvailableEndpoints).To(BeNumerically(">=", 0))
})
})
114 changes: 114 additions & 0 deletions e2e/slice_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package e2e

import (
"context"
"time"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"

kubeslicev1beta1 "github.com/kubeslice/worker-operator/api/v1beta1"
)

var _ = Describe("Slice E2E", func() {
var (
ctx context.Context
namespace string
sliceName string
)

BeforeEach(func() {
ctx = context.Background()
namespace = "test-namespace"
// sliceName = "test-slice"
sliceName = "e2e-slice"

// Create namespace if not exists
createNamespaceIfNotExists(ctx, namespace)
})

It("should create a Slice and reconcile its status", func() {
// Create Slice object
slice := &kubeslicev1beta1.Slice{
ObjectMeta: metav1.ObjectMeta{
Name: sliceName,
Namespace: namespace,
Labels: map[string]string{
"kubeslice.io/origin": "hub",
},
Annotations: map[string]string{
"kubeslice.io/reconciled-from-hub": "true",
},
},
Spec: kubeslicev1beta1.SliceSpec{},
}
Expect(k8sClient.Create(ctx, slice)).To(Succeed())

Eventually(func(g Gomega) {
updated := &kubeslicev1beta1.Slice{}
g.Expect(k8sClient.Get(ctx, client.ObjectKey{Name: sliceName, Namespace: namespace}, updated)).To(Succeed())
g.Expect(updated.Status.SliceConfig).NotTo(BeNil())
}).WithTimeout(2 * time.Minute).WithPolling(2 * time.Second).Should(Succeed())
})

It("should update app pods in Slice status when pods are created", func() {
// Create the Slice first
slice := &kubeslicev1beta1.Slice{
ObjectMeta: metav1.ObjectMeta{
Name: sliceName,
Namespace: namespace,
Labels: map[string]string{
"kubeslice.io/origin": "hub",
},
Annotations: map[string]string{
"kubeslice.io/reconciled-from-hub": "true",
},
},
Spec: kubeslicev1beta1.SliceSpec{},
}
Expect(k8sClient.Create(ctx, slice)).To(Succeed())

// Create a test app pod with the application namespace selector label
labels := map[string]string{
"app": "test-app",
"kubeslice.io/application-namespace": sliceName,
}

createTestPod(ctx, namespace, "app-pod", labels)

Eventually(func(g Gomega) {
updated := &kubeslicev1beta1.Slice{}
g.Expect(k8sClient.Get(ctx, client.ObjectKey{Name: sliceName, Namespace: namespace}, updated)).To(Succeed())
g.Expect(len(updated.Status.AppPods)).To(BeNumerically(">", 0))
g.Expect(updated.Status.AppPods[0].PodName).To(Equal("app-pod"))
}).WithTimeout(2 * time.Minute).WithPolling(2 * time.Second).Should(Succeed())
})

It("should delete Slice successfully", func() {
// Create Slice
slice := &kubeslicev1beta1.Slice{
ObjectMeta: metav1.ObjectMeta{
Name: sliceName,
Namespace: namespace,
Labels: map[string]string{
"kubeslice.io/origin": "hub",
},
Annotations: map[string]string{
"kubeslice.io/reconciled-from-hub": "true",
},
},
Spec: kubeslicev1beta1.SliceSpec{},
}
Expect(k8sClient.Create(ctx, slice)).To(Succeed())

Expect(k8sClient.Delete(ctx, slice)).To(Succeed())

// Verify Slice is deleted
Eventually(func() error {
s := &kubeslicev1beta1.Slice{}
return k8sClient.Get(ctx, client.ObjectKey{Name: sliceName, Namespace: namespace}, s)
}).WithTimeout(1 * time.Minute).WithPolling(2 * time.Second).ShouldNot(Succeed())
})
})
109 changes: 109 additions & 0 deletions e2e/slicegateway_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package e2e

import (
"context"
"time"

kubeslicev1beta1 "github.com/kubeslice/worker-operator/api/v1beta1"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"

corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
)

var _ = Describe("SliceGateway E2E", Ordered, func() {
var (
ctx context.Context
sliceGwName string
sliceGwNamespace string
sliceName string
siteName string
)

BeforeAll(func() {
ctx = context.Background()
sliceGwName = "e2e-slicegateway"
sliceGwNamespace = "kubeslice-system" // control plane namespace
sliceName = "e2e-slice"
siteName = "e2e-site"

// Create the namespace so the test doesn't fail
createNamespaceIfNotExists(ctx, sliceGwNamespace)
})

AfterAll(func() {
// Cleanup resources at the end of test
gw := &kubeslicev1beta1.SliceGateway{}
err := k8sClient.Get(ctx, types.NamespacedName{Name: sliceGwName, Namespace: sliceGwNamespace}, gw)
if err == nil {
_ = k8sClient.Delete(ctx, gw)
}
})

It("should create a SliceGateway CR successfully", func() {
gw := &kubeslicev1beta1.SliceGateway{
ObjectMeta: metav1.ObjectMeta{
Name: sliceGwName,
Namespace: sliceGwNamespace,
},
Spec: kubeslicev1beta1.SliceGatewaySpec{
SliceName: sliceName,
SiteName: siteName,
},
}

By("creating the SliceGateway resource")
Expect(k8sClient.Create(ctx, gw)).To(Succeed())

By("verifying the resource exists")
created := &kubeslicev1beta1.SliceGateway{}
Eventually(func() error {
return k8sClient.Get(ctx, types.NamespacedName{Name: sliceGwName, Namespace: sliceGwNamespace}, created)
}, 60*time.Second, 5*time.Second).Should(Succeed())

Expect(created.Spec.SliceName).To(Equal(sliceName))
Expect(created.Spec.SiteName).To(Equal(siteName))
})

It("should update SliceGateway status when reconciled", func() {
By("waiting for controller to reconcile")
Eventually(func() string {
gw := &kubeslicev1beta1.SliceGateway{}
if err := k8sClient.Get(ctx, types.NamespacedName{Name: sliceGwName, Namespace: sliceGwNamespace}, gw); err != nil {
return ""
}
return gw.Status.Config.SliceGatewayStatus
}, 2*time.Minute, 5*time.Second).ShouldNot(BeEmpty())
})

It("should have gateway pod created by controller", func() {
By("verifying a gateway pod exists")
Eventually(func() int {
podList := &corev1.PodList{}
err := k8sClient.List(ctx, podList, client.InNamespace(sliceGwNamespace),
client.MatchingLabels(map[string]string{
"networking.kubeslice.io/slicegateway": sliceGwName,
}))
if err != nil {
return 0
}
return len(podList.Items)
}, 2*time.Minute, 10*time.Second).Should(BeNumerically(">", 0))
})

It("should delete SliceGateway successfully", func() {
By("deleting the SliceGateway")
gw := &kubeslicev1beta1.SliceGateway{}
Expect(k8sClient.Get(ctx, types.NamespacedName{Name: sliceGwName, Namespace: sliceGwNamespace}, gw)).To(Succeed())
Expect(k8sClient.Delete(ctx, gw)).To(Succeed())

By("verifying the resource is deleted")
Eventually(func() bool {
err := k8sClient.Get(ctx, types.NamespacedName{Name: sliceGwName, Namespace: sliceGwNamespace}, gw)
return err != nil
}, 60*time.Second, 5*time.Second).Should(BeTrue())
})
})
Loading