This is an automated email from the ASF dual-hosted git repository. ashishtiwari pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/apisix-ingress-controller.git
The following commit(s) were added to refs/heads/master by this push: new ac5e56dd chore: add test cases for external service (#2500) ac5e56dd is described below commit ac5e56dd5e44142370ee54b72876faa20c098b5f Author: Ashish Tiwari <ashishjaitiwari15112...@gmail.com> AuthorDate: Tue Aug 5 20:03:34 2025 +0530 chore: add test cases for external service (#2500) --- test/e2e/crds/v2/route.go | 261 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 261 insertions(+) diff --git a/test/e2e/crds/v2/route.go b/test/e2e/crds/v2/route.go index dbd42e5c..fdc1cd5a 100644 --- a/test/e2e/crds/v2/route.go +++ b/test/e2e/crds/v2/route.go @@ -30,6 +30,7 @@ import ( "github.com/gorilla/websocket" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "github.com/stretchr/testify/assert" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/wait" @@ -802,6 +803,7 @@ spec: } }) }) + Context("Test ApisixRoute sync during startup", func() { const route = ` apiVersion: apisix.apache.org/v2 @@ -1041,4 +1043,263 @@ spec: Expect(string(msg)).To(Equal(testMessage), "message content verification") }) }) + + Context("Test ApisixRoute with External Services", func() { + const ( + externalServiceName = "ext-httpbin" + upstreamName = "httpbin-upstream" + routeName = "httpbin-route" + ) + + createExternalService := func(externalName string) { + By(fmt.Sprintf("create ExternalName service: %s -> %s", externalServiceName, externalName)) + svcSpec := fmt.Sprintf(` +apiVersion: v1 +kind: Service +metadata: + name: %s +spec: + type: ExternalName + externalName: %s +`, externalServiceName, externalName) + err := s.CreateResourceFromString(svcSpec) + Expect(err).ShouldNot(HaveOccurred(), "creating ExternalName service") + } + + createApisixUpstream := func(externalType apiv2.ApisixUpstreamExternalType, name string) { + By(fmt.Sprintf("create ApisixUpstream: type=%s, name=%s", externalType, name)) + upstreamSpec := fmt.Sprintf(` +apiVersion: apisix.apache.org/v2 +kind: ApisixUpstream +metadata: + name: %s +spec: + externalNodes: + - type: %s + name: %s +`, upstreamName, externalType, name) + var upstream apiv2.ApisixUpstream + applier.MustApplyAPIv2( + types.NamespacedName{Namespace: s.Namespace(), Name: upstreamName}, + &upstream, + upstreamSpec, + ) + } + + createApisixRoute := func() { + By("create ApisixRoute referencing ApisixUpstream") + routeSpec := fmt.Sprintf(` +apiVersion: apisix.apache.org/v2 +kind: ApisixRoute +metadata: + name: %s +spec: + ingressClassName: apisix + http: + - name: rule1 + match: + hosts: + - httpbin.org + paths: + - /ip + upstreams: + - name: %s +`, routeName, upstreamName) + var route apiv2.ApisixRoute + applier.MustApplyAPIv2( + types.NamespacedName{Namespace: s.Namespace(), Name: routeName}, + &route, + routeSpec, + ) + } + + createApisixRouteWithHostRewrite := func(host string) { + By("create ApisixRoute with host rewrite") + routeSpec := fmt.Sprintf(` +apiVersion: apisix.apache.org/v2 +kind: ApisixRoute +metadata: + name: %s +spec: + ingressClassName: apisix + http: + - name: rule1 + match: + hosts: + - httpbin.org + paths: + - /ip + upstreams: + - name: %s + plugins: + - name: proxy-rewrite + enable: true + config: + host: %s +`, routeName, upstreamName, host) + var route apiv2.ApisixRoute + applier.MustApplyAPIv2( + types.NamespacedName{Namespace: s.Namespace(), Name: routeName}, + &route, + routeSpec, + ) + } + + verifyAccess := func() { + By("verify access to external service") + request := func() int { + return s.NewAPISIXClient().GET("/ip"). + WithHost("httpbin.org"). + Expect().Raw().StatusCode + } + Eventually(request).WithTimeout(30 * time.Second).ProbeEvery(2 * time.Second). + Should(Equal(http.StatusOK)) + } + + It("access third-party service directly", func() { + createApisixUpstream(apiv2.ExternalTypeDomain, "httpbin.org") + createApisixRoute() + verifyAccess() + }) + + It("access third-party service with host rewrite", func() { + createApisixUpstream(apiv2.ExternalTypeDomain, "httpbin.org") + createApisixRouteWithHostRewrite("httpbin.org") + verifyAccess() + }) + + It("access external domain via ExternalName service", func() { + createExternalService("httpbin.org") + createApisixUpstream(apiv2.ExternalTypeService, externalServiceName) + createApisixRoute() + verifyAccess() + }) + + It("access in-cluster service via ExternalName", func() { + By("create temporary httpbin service") + + By("get FQDN of temporary service") + fqdn := fmt.Sprintf("%s.%s.svc.cluster.local", "httpbin-service-e2e-test", s.Namespace()) + + By("setup external service and route") + createExternalService(fqdn) + createApisixUpstream(apiv2.ExternalTypeService, externalServiceName) + createApisixRoute() + verifyAccess() + }) + + Context("complex scenarios", func() { + It("multiple external services in one upstream", func() { + By("create ApisixUpstream with multiple external nodes") + upstreamSpec := ` +apiVersion: apisix.apache.org/v2 +kind: ApisixUpstream +metadata: + name: httpbin-upstream +spec: + externalNodes: + - type: Domain + name: httpbin.org + - type: Domain + name: postman-echo.com +` + var upstream apiv2.ApisixUpstream + applier.MustApplyAPIv2( + types.NamespacedName{Namespace: s.Namespace(), Name: upstreamName}, + &upstream, + upstreamSpec, + ) + + createApisixRoute() + + By("verify access to multiple services") + time.Sleep(7 * time.Second) + hasEtag := false // postman-echo.com + hasNoEtag := false // httpbin.org + for range 20 { + headers := s.NewAPISIXClient().GET("/ip"). + WithHeader("Host", "httpbin.org"). + WithHeader("X-Foo", "bar"). + Expect(). + Headers().Raw() + if _, ok := headers["Etag"]; ok { + hasEtag = true + } else { + hasNoEtag = true + } + if hasEtag && hasNoEtag { + break + } + } + assert.True(GinkgoT(), hasEtag && hasNoEtag, "both httpbin and postman should be accessed at least once") + }) + + It("should be able to use backends and upstreams together", func() { + upstreamSpec := ` +apiVersion: apisix.apache.org/v2 +kind: ApisixUpstream +metadata: + name: httpbin-upstream +spec: + externalNodes: + - type: Domain + name: postman-echo.com +` + var upstream apiv2.ApisixUpstream + applier.MustApplyAPIv2( + types.NamespacedName{Namespace: s.Namespace(), Name: upstreamName}, + &upstream, + upstreamSpec, + ) + By("create ApisixRoute with both backends and upstreams") + routeSpec := fmt.Sprintf(` +apiVersion: apisix.apache.org/v2 +kind: ApisixRoute +metadata: + name: %s +spec: + ingressClassName: apisix + http: + - name: rule1 + match: + hosts: + - httpbin.org + paths: + - /ip + backends: + - serviceName: httpbin-service-e2e-test + servicePort: 80 + resolveGranularity: service + upstreams: + - name: %s +`, routeName, upstreamName) + var route apiv2.ApisixRoute + applier.MustApplyAPIv2( + types.NamespacedName{Namespace: s.Namespace(), Name: routeName}, + &route, + routeSpec, + ) + By("verify access to multiple services") + time.Sleep(7 * time.Second) + hasEtag := false // postman-echo.com + hasNoEtag := false // httpbin.org + for range 20 { + headers := s.NewAPISIXClient().GET("/ip"). + WithHeader("Host", "httpbin.org"). + WithHeader("X-Foo", "bar"). + Expect(). + Headers().Raw() + if _, ok := headers["Etag"]; ok { + hasEtag = true + } else { + hasNoEtag = true + } + if hasEtag && hasNoEtag { + break + } + } + assert.True(GinkgoT(), hasEtag && hasNoEtag, "both httpbin and postman should be accessed at least once") + }) + }) + }) })