diff --git a/internal/controller/ingressclass_controller.go b/internal/controller/ingressclass_controller.go index c0e88f2de..d17783235 100644 --- a/internal/controller/ingressclass_controller.go +++ b/internal/controller/ingressclass_controller.go @@ -191,6 +191,10 @@ func (r *IngressClassReconciler) processInfrastructure(tctx *provider.TranslateC if ingressClass.Spec.Parameters.Namespace != nil { namespace = *ingressClass.Spec.Parameters.Namespace } + // Check for annotation override + if annotationNamespace, exists := ingressClass.Annotations[parametersNamespaceAnnotation]; exists && annotationNamespace != "" { + namespace = annotationNamespace + } gatewayProxy := new(v1alpha1.GatewayProxy) if err := r.Get(context.Background(), client.ObjectKey{ diff --git a/internal/controller/ingressclass_v1beta1_controller.go b/internal/controller/ingressclass_v1beta1_controller.go index 70aa86ca1..c5764a646 100644 --- a/internal/controller/ingressclass_v1beta1_controller.go +++ b/internal/controller/ingressclass_v1beta1_controller.go @@ -188,12 +188,17 @@ func (r *IngressClassV1beta1Reconciler) processInfrastructure(tctx *provider.Tra } // Since v1beta1 does not support specifying the target namespace, - // and GatewayProxy is a namespace-scoped resource, we default to using - // the "default" namespace for convenience. + // and GatewayProxy is a namespace-scoped resource, we first check + // for the annotation, then fall back to the spec field, and finally + // default to "default" namespace for convenience. namespace := "default" if IngressClassV1beta1.Spec.Parameters.Namespace != nil { namespace = *IngressClassV1beta1.Spec.Parameters.Namespace } + // Check for annotation override + if annotationNamespace, exists := IngressClassV1beta1.Annotations[parametersNamespaceAnnotation]; exists && annotationNamespace != "" { + namespace = annotationNamespace + } gatewayProxy := new(v1alpha1.GatewayProxy) if err := r.Get(tctx, client.ObjectKey{ diff --git a/internal/controller/utils.go b/internal/controller/utils.go index 7df386283..ea22435cb 100644 --- a/internal/controller/utils.go +++ b/internal/controller/utils.go @@ -77,7 +77,10 @@ const ( KindApisixConsumer = "ApisixConsumer" ) -const defaultIngressClassAnnotation = "ingressclass.kubernetes.io/is-default-class" +const ( + defaultIngressClassAnnotation = "ingressclass.kubernetes.io/is-default-class" + parametersNamespaceAnnotation = "apisix.apache.org/parameters-namespace" +) var ( ErrNoMatchingListenerHostname = errors.New("no matching hostnames in listener") @@ -1323,6 +1326,9 @@ func ProcessIngressClassParameters(tctx *provider.TranslateContext, c client.Cli if parameters.Namespace != nil { ns = *parameters.Namespace } + if annotationNamespace, exists := ingressClass.Annotations[parametersNamespaceAnnotation]; exists && annotationNamespace != "" { + ns = annotationNamespace + } gatewayProxy := &v1alpha1.GatewayProxy{} if err := c.Get(tctx, client.ObjectKey{ diff --git a/test/e2e/crds/v2/basic.go b/test/e2e/crds/v2/basic.go index 5538b4134..8636d03ef 100644 --- a/test/e2e/crds/v2/basic.go +++ b/test/e2e/crds/v2/basic.go @@ -18,9 +18,16 @@ package v2 import ( + "fmt" + "net/http" + "time" + . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "k8s.io/apimachinery/pkg/types" + apiv2 "github.com/apache/apisix-ingress-controller/api/v2" + "github.com/apache/apisix-ingress-controller/test/e2e/framework" "github.com/apache/apisix-ingress-controller/test/e2e/scaffold" ) @@ -56,11 +63,14 @@ spec: ` var _ = Describe("APISIX Standalone Basic Tests", Label("apisix.apache.org", "v2", "basic"), func() { - s := scaffold.NewScaffold(&scaffold.Options{ - ControllerName: "apisix.apache.org/apisix-ingress-controller", - }) + var ( + s = scaffold.NewScaffold(&scaffold.Options{ + ControllerName: "apisix.apache.org/apisix-ingress-controller", + }) + applier = framework.NewApplier(s.GinkgoT, s.K8sClient, s.CreateResourceFromString) + ) - Describe("APISIX HTTP Proxy", func() { + Context("APISIX HTTP Proxy", func() { It("should handle basic HTTP requests", func() { httpClient := s.NewAPISIXClient() Expect(httpClient).NotTo(BeNil()) @@ -83,6 +93,97 @@ var _ = Describe("APISIX Standalone Basic Tests", Label("apisix.apache.org", "v2 Expect(). Status(404).Body().Contains("404 Route Not Found") }) + }) + + Context("IngressClass Annotations", func() { + It("Basic tests", func() { + const gatewayProxyYaml = ` +apiVersion: apisix.apache.org/v1alpha1 +kind: GatewayProxy +metadata: + name: apisix-proxy-config +spec: + provider: + type: ControlPlane + controlPlane: + endpoints: + - %s + auth: + type: AdminKey + adminKey: + value: "%s" +` + const ingressClassYaml = ` +apiVersion: networking.k8s.io/%s +kind: IngressClass +metadata: + name: apisix + annotations: + apisix.apache.org/parameters-namespace: %s +spec: + controller: apisix.apache.org/apisix-ingress-controller + parameters: + apiGroup: apisix.apache.org + kind: GatewayProxy + name: apisix-proxy-config +` + + By("create GatewayProxy") + gatewayProxy := fmt.Sprintf(gatewayProxyYaml, s.Deployer.GetAdminEndpoint(), s.AdminKey()) + err := s.CreateResourceFromString(gatewayProxy) + Expect(err).NotTo(HaveOccurred(), "creating GatewayProxy") + time.Sleep(5 * time.Second) + + By("create IngressClass") + ingressClass := fmt.Sprintf(ingressClassYaml, framework.IngressVersion, s.Namespace()) + err = s.CreateResourceFromString(ingressClass) + Expect(err).NotTo(HaveOccurred(), "creating IngressClass") + time.Sleep(5 * time.Second) + + const apisixRouteSpec = ` +apiVersion: apisix.apache.org/v2 +kind: ApisixRoute +metadata: + name: default +spec: + ingressClassName: apisix + http: + - name: rule0 + match: + hosts: + - httpbin + paths: + - %s + backends: + - serviceName: httpbin-service-e2e-test + servicePort: 80 +` + request := func(path string) int { + return s.NewAPISIXClient().GET(path).WithHost("httpbin").Expect().Raw().StatusCode + } + + By("apply ApisixRoute") + var apisixRoute apiv2.ApisixRoute + applier.MustApplyAPIv2(types.NamespacedName{Namespace: s.Namespace(), Name: "default"}, &apisixRoute, fmt.Sprintf(apisixRouteSpec, "/get")) + + By("verify ApisixRoute works") + Eventually(request).WithArguments("/get").WithTimeout(8 * time.Second).ProbeEvery(time.Second).Should(Equal(http.StatusOK)) + + By("update ApisixRoute") + applier.MustApplyAPIv2(types.NamespacedName{Namespace: s.Namespace(), Name: "default"}, &apisixRoute, fmt.Sprintf(apisixRouteSpec, "/headers")) + Eventually(request).WithArguments("/get").WithTimeout(8 * time.Second).ProbeEvery(time.Second).Should(Equal(http.StatusNotFound)) + s.RequestAssert(&scaffold.RequestAssert{ + Method: "GET", + Path: "/headers", + Host: "httpbin", + Check: scaffold.WithExpectedStatus(http.StatusOK), + }) + + By("delete ApisixRoute") + err = s.DeleteResource("ApisixRoute", "default") + Expect(err).ShouldNot(HaveOccurred(), "deleting ApisixRoute") + Eventually(request).WithArguments("/headers").WithTimeout(8 * time.Second).ProbeEvery(time.Second).Should(Equal(http.StatusNotFound)) + }) }) }) diff --git a/test/e2e/crds/v2/route.go b/test/e2e/crds/v2/route.go index 873dde01f..a3339aff2 100644 --- a/test/e2e/crds/v2/route.go +++ b/test/e2e/crds/v2/route.go @@ -132,9 +132,6 @@ spec: Expect(bodyStr).Should(ContainSubstring("apisix_ingress_adc_sync_total")) Expect(bodyStr).Should(ContainSubstring("apisix_ingress_status_update_queue_length")) Expect(bodyStr).Should(ContainSubstring("apisix_ingress_file_io_duration_seconds")) - - // Log metrics for debugging - fmt.Printf("Metrics endpoint response:\n%s\n", bodyStr) }) It("Test plugins in ApisixRoute", func() {