Files
kamaji/e2e/tcp_gateway_konnectivity_ready_test.go
Parth Yadav 87242ff005 feat: extend Gateway API support to Konnectivity addons (#1054)
This change extends Gateway API support to Konnectivity addons.
When `spec.controlPlane.gateway` is configured and Konnectivity addon is
enabled, Kamaji automatically creates two TLSRoutes:
1. A Control plane TLSRoute (port 6443, sectionName "kube-apiserver")
2. A Konnectivity TLSRoute (port 8132, sectionName "konnectivity-server")

Both routes use the hostname specified in `gateway.hostname` and reference
the same Gateway resource via `parentRefs`, with `port` and `sectionName`
set automatically by Kamaji.

This patch also adds CEL validation to prevent users from specifying
`port` or `sectionName` in Gateway `parentRefs`, as these fields are now
managed automatically by Kamaji.

Signed-off-by: Parth Yadav <parth@coredge.io>
2026-01-11 11:31:24 +01:00

152 lines
4.5 KiB
Go

// Copyright 2022 Clastix Labs
// SPDX-License-Identifier: Apache-2.0
package e2e
import (
"context"
"fmt"
"time"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
pointer "k8s.io/utils/ptr"
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
)
var _ = Describe("Deploy a TenantControlPlane with Gateway API and Konnectivity", func() {
var tcp *kamajiv1alpha1.TenantControlPlane
JustBeforeEach(func() {
tcp = &kamajiv1alpha1.TenantControlPlane{
ObjectMeta: metav1.ObjectMeta{
Name: "tcp-konnectivity-gateway",
Namespace: "default",
},
Spec: kamajiv1alpha1.TenantControlPlaneSpec{
ControlPlane: kamajiv1alpha1.ControlPlane{
Deployment: kamajiv1alpha1.DeploymentSpec{
Replicas: pointer.To(int32(1)),
},
Service: kamajiv1alpha1.ServiceSpec{
ServiceType: "ClusterIP",
},
Gateway: &kamajiv1alpha1.GatewaySpec{
Hostname: gatewayv1.Hostname("tcp-gateway-konnectivity.example.com"),
AdditionalMetadata: kamajiv1alpha1.AdditionalMetadata{
Labels: map[string]string{
"test.kamaji.io/gateway": "true",
},
Annotations: map[string]string{
"test.kamaji.io/created-by": "e2e-test",
},
},
GatewayParentRefs: []gatewayv1.ParentReference{
{
Name: "test-gateway",
},
},
},
},
NetworkProfile: kamajiv1alpha1.NetworkProfileSpec{
Address: "172.18.0.4",
},
Kubernetes: kamajiv1alpha1.KubernetesSpec{
Version: "v1.28.0",
Kubelet: kamajiv1alpha1.KubeletSpec{
CGroupFS: "cgroupfs",
},
AdmissionControllers: kamajiv1alpha1.AdmissionControllers{
"LimitRanger",
"ResourceQuota",
},
},
Addons: kamajiv1alpha1.AddonsSpec{
Konnectivity: &kamajiv1alpha1.KonnectivitySpec{
KonnectivityServerSpec: kamajiv1alpha1.KonnectivityServerSpec{
Port: 8132,
},
},
},
},
}
Expect(k8sClient.Create(context.Background(), tcp)).NotTo(HaveOccurred())
})
JustAfterEach(func() {
Expect(k8sClient.Delete(context.Background(), tcp)).Should(Succeed())
// Wait for the object to be completely deleted
Eventually(func() bool {
err := k8sClient.Get(context.Background(), types.NamespacedName{
Name: tcp.Name,
Namespace: tcp.Namespace,
}, &kamajiv1alpha1.TenantControlPlane{})
return err != nil // Returns true when object is not found (deleted)
}).WithTimeout(time.Minute).Should(BeTrue())
})
It("Should be Ready", func() {
StatusMustEqualTo(tcp, kamajiv1alpha1.VersionReady)
})
It("Should create Konnectivity TLSRoute with correct sectionName", func() {
Eventually(func() error {
route := &gatewayv1alpha2.TLSRoute{}
if err := k8sClient.Get(context.Background(), types.NamespacedName{
Name: tcp.Name + "-konnectivity",
Namespace: tcp.Namespace,
}, route); err != nil {
return err
}
if len(route.Spec.ParentRefs) == 0 {
return fmt.Errorf("parentRefs is empty")
}
if route.Spec.ParentRefs[0].SectionName == nil {
return fmt.Errorf("sectionName is nil")
}
if *route.Spec.ParentRefs[0].SectionName != gatewayv1.SectionName("konnectivity-server") {
return fmt.Errorf("expected sectionName 'konnectivity-server', got '%s'", *route.Spec.ParentRefs[0].SectionName)
}
return nil
}).WithTimeout(time.Minute).Should(Succeed())
})
It("Should use same hostname for both TLSRoutes", func() {
Eventually(func() error {
controlPlaneRoute := &gatewayv1alpha2.TLSRoute{}
if err := k8sClient.Get(context.Background(), types.NamespacedName{
Name: tcp.Name,
Namespace: tcp.Namespace,
}, controlPlaneRoute); err != nil {
return err
}
konnectivityRoute := &gatewayv1alpha2.TLSRoute{}
if err := k8sClient.Get(context.Background(), types.NamespacedName{
Name: tcp.Name + "-konnectivity",
Namespace: tcp.Namespace,
}, konnectivityRoute); err != nil {
return err
}
if len(controlPlaneRoute.Spec.Hostnames) == 0 || len(konnectivityRoute.Spec.Hostnames) == 0 {
return fmt.Errorf("hostnames are empty")
}
if controlPlaneRoute.Spec.Hostnames[0] != konnectivityRoute.Spec.Hostnames[0] {
return fmt.Errorf("hostnames do not match: control plane '%s', konnectivity '%s'",
controlPlaneRoute.Spec.Hostnames[0], konnectivityRoute.Spec.Hostnames[0])
}
return nil
}).WithTimeout(time.Minute).Should(Succeed())
})
})