You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@apisix.apache.org by al...@apache.org on 2025/07/07 02:03:14 UTC

(apisix-ingress-controller) branch v2.0.0 updated (18f03ea6 -> 43bbe768)

This is an automated email from the ASF dual-hosted git repository.

alinsran pushed a change to branch v2.0.0
in repository https://gitbox.apache.org/repos/asf/apisix-ingress-controller.git


    from 18f03ea6 fix: add more event filter across controllers (#2449)
     new d8be46e7 fix: support filter endpoint when translate backend ref. (#2451)
     new bce4c696 fix: a failing endpoint shouldn't affect others (#2452)
     new 4f22fb6a fix: config not provided should not be retried (#2454)
     new 43bbe768 fix: apisixroute backend service reference to apisixupstream (#2453)

The 4 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 internal/controller/apisixroute_controller.go   |  60 +++++-----
 internal/controller/indexer/indexer.go          |   2 +-
 internal/provider/adc/adc.go                    |  35 ++++--
 internal/provider/adc/config.go                 |   9 +-
 internal/provider/adc/executor.go               |   8 +-
 internal/provider/adc/translator/apisixroute.go |  66 +++++------
 internal/provider/adc/translator/httproute.go   |  23 +++-
 internal/provider/adc/translator/ingress.go     |   3 +
 test/e2e/apisix/route.go                        |  75 +++++++++++-
 test/e2e/apiv2/apisixconsumer.go                |  18 ---
 test/e2e/apiv2/apisixglobalrule.go              |  18 ---
 test/e2e/apiv2/apisixpluginconfig.go            |  18 ---
 test/e2e/apiv2/apisixroute.go                   |  18 ---
 test/e2e/apiv2/apisixtls.go                     |  18 ---
 test/e2e/apiv2/apisixupstream.go                |  18 ---
 test/e2e/gatewayapi/gatewayproxy.go             | 146 ++++++------------------
 16 files changed, 227 insertions(+), 308 deletions(-)
 delete mode 100644 test/e2e/apiv2/apisixconsumer.go
 delete mode 100644 test/e2e/apiv2/apisixglobalrule.go
 delete mode 100644 test/e2e/apiv2/apisixpluginconfig.go
 delete mode 100644 test/e2e/apiv2/apisixroute.go
 delete mode 100644 test/e2e/apiv2/apisixtls.go
 delete mode 100644 test/e2e/apiv2/apisixupstream.go


(apisix-ingress-controller) 03/04: fix: config not provided should not be retried (#2454)

Posted by al...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

alinsran pushed a commit to branch v2.0.0
in repository https://gitbox.apache.org/repos/asf/apisix-ingress-controller.git

commit 4f22fb6a4cf195a77cfd488603bf2929d3f24f44
Author: AlinsRan <al...@apache.org>
AuthorDate: Sat Jul 5 09:10:12 2025 +0800

    fix: config not provided should not be retried (#2454)
---
 internal/provider/adc/adc.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/internal/provider/adc/adc.go b/internal/provider/adc/adc.go
index e8c44694..0ab02d44 100644
--- a/internal/provider/adc/adc.go
+++ b/internal/provider/adc/adc.go
@@ -351,8 +351,8 @@ func (d *adcClient) sync(ctx context.Context, task Task) error {
 	log.Debugw("syncing resources", zap.Any("task", task))
 
 	if len(task.configs) == 0 {
-		log.Errorw("no adc configs provided", zap.Any("task", task))
-		return errors.New("no adc configs provided")
+		log.Warnw("no adc configs provided", zap.Any("task", task))
+		return nil
 	}
 
 	syncFilePath, cleanup, err := prepareSyncFile(task.Resources)


(apisix-ingress-controller) 02/04: fix: a failing endpoint shouldn't affect others (#2452)

Posted by al...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

alinsran pushed a commit to branch v2.0.0
in repository https://gitbox.apache.org/repos/asf/apisix-ingress-controller.git

commit bce4c6963c54d4c97ad3eef6f09d2d85e475dbce
Author: AlinsRan <al...@apache.org>
AuthorDate: Fri Jul 4 18:13:37 2025 +0800

    fix: a failing endpoint shouldn't affect others (#2452)
---
 internal/provider/adc/adc.go        |  31 +++++---
 internal/provider/adc/executor.go   |   8 +-
 test/e2e/gatewayapi/gatewayproxy.go | 146 +++++++++---------------------------
 3 files changed, 66 insertions(+), 119 deletions(-)

diff --git a/internal/provider/adc/adc.go b/internal/provider/adc/adc.go
index ae0a4513..e8c44694 100644
--- a/internal/provider/adc/adc.go
+++ b/internal/provider/adc/adc.go
@@ -20,7 +20,9 @@ package adc
 import (
 	"context"
 	"encoding/json"
+	"fmt"
 	"os"
+	"strings"
 	"sync"
 	"time"
 
@@ -277,6 +279,7 @@ func (d *adcClient) Start(ctx context.Context) error {
 	initalSyncDelay := d.InitSyncDelay
 	time.AfterFunc(initalSyncDelay, func() {
 		if err := d.Sync(ctx); err != nil {
+			log.Error(err)
 			return
 		}
 	})
@@ -290,7 +293,7 @@ func (d *adcClient) Start(ctx context.Context) error {
 		select {
 		case <-ticker.C:
 			if err := d.Sync(ctx); err != nil {
-				log.Errorw("failed to sync resources", zap.Error(err))
+				log.Error(err)
 			}
 		case <-ctx.Done():
 			return nil
@@ -315,25 +318,32 @@ func (d *adcClient) Sync(ctx context.Context) error {
 
 	log.Debugw("syncing resources with multiple configs", zap.Any("configs", cfg))
 
+	var failedConfigs []string
 	for name, config := range cfg {
 		resources, err := d.store.GetResources(name)
 		if err != nil {
-			return err
+			log.Errorw("failed to get resources from store", zap.String("name", name), zap.Error(err))
+			failedConfigs = append(failedConfigs, name)
+			continue
 		}
 		if resources == nil {
 			continue
 		}
 
-		err = d.sync(ctx, Task{
+		if err := d.sync(ctx, Task{
 			Name:      name + "-sync",
 			configs:   []adcConfig{config},
 			Resources: *resources,
-		})
-		if err != nil {
-			return err
+		}); err != nil {
+			log.Errorw("failed to sync resources", zap.String("name", name), zap.Error(err))
+			failedConfigs = append(failedConfigs, name)
 		}
 	}
-
+	if len(failedConfigs) > 0 {
+		return fmt.Errorf("failed to sync %d configs: %s",
+			len(failedConfigs),
+			strings.Join(failedConfigs, ", "))
+	}
 	return nil
 }
 
@@ -353,13 +363,16 @@ func (d *adcClient) sync(ctx context.Context, task Task) error {
 
 	args := BuildADCExecuteArgs(syncFilePath, task.Labels, task.ResourceTypes)
 
-	log.Debugw("syncing resources with multiple configs", zap.Any("configs", task.configs))
+	var failedConfigs []string
 	for _, config := range task.configs {
 		if err := d.executor.Execute(ctx, d.BackendMode, config, args); err != nil {
 			log.Errorw("failed to execute adc command", zap.Error(err), zap.Any("config", config))
-			return err
+			failedConfigs = append(failedConfigs, config.Name)
 		}
 	}
+	if len(failedConfigs) > 0 {
+		return fmt.Errorf("failed to execute adc command for configs: %s", strings.Join(failedConfigs, ", "))
+	}
 	return nil
 }
 
diff --git a/internal/provider/adc/executor.go b/internal/provider/adc/executor.go
index 304739e2..bd7b6c7f 100644
--- a/internal/provider/adc/executor.go
+++ b/internal/provider/adc/executor.go
@@ -22,6 +22,7 @@ import (
 	"context"
 	"encoding/json"
 	"errors"
+	"fmt"
 	"os"
 	"os/exec"
 	"strings"
@@ -50,11 +51,16 @@ func (e *DefaultADCExecutor) Execute(ctx context.Context, mode string, config ad
 }
 
 func (e *DefaultADCExecutor) runADC(ctx context.Context, mode string, config adcConfig, args []string) error {
+	var failedAddrs []string
 	for _, addr := range config.ServerAddrs {
 		if err := e.runForSingleServerWithTimeout(ctx, addr, mode, config, args); err != nil {
-			return err
+			log.Errorw("failed to run adc for server", zap.String("server", addr), zap.Error(err))
+			failedAddrs = append(failedAddrs, addr)
 		}
 	}
+	if len(failedAddrs) > 0 {
+		return fmt.Errorf("failed to run adc for servers: [%s]", strings.Join(failedAddrs, ", "))
+	}
 	return nil
 }
 
diff --git a/test/e2e/gatewayapi/gatewayproxy.go b/test/e2e/gatewayapi/gatewayproxy.go
index 6bac0590..dfcbba9b 100644
--- a/test/e2e/gatewayapi/gatewayproxy.go
+++ b/test/e2e/gatewayapi/gatewayproxy.go
@@ -19,7 +19,6 @@ package gatewayapi
 
 import (
 	"fmt"
-	"net/http"
 	"time"
 
 	. "github.com/onsi/ginkgo/v2"
@@ -103,64 +102,6 @@ spec:
       headers:
         X-Proxy-Test: "disabled"
 `
-	var (
-		gatewayProxyWithPluginMetadata0 = `
-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"
-  plugins:
-  - name: error-page
-    enabled: true
-    config: {}
-  pluginMetadata:
-    error-page: {
-      "enable": true,
-      "error_404": {
-          "body": "404 from plugin metadata",
-          "content-type": "text/plain"
-      }
-    }
-`
-		gatewayProxyWithPluginMetadata1 = `
-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"
-  plugins:
-  - name: error-page
-    enabled: true
-    config: {}
-  pluginMetadata:
-    error-page: {
-      "enable": false,
-      "error_404": {
-          "body": "404 from plugin metadata",
-          "content-type": "text/plain"
-      }
-    }
-`
-	)
 
 	var httpRouteForTest = `
 apiVersion: gateway.networking.k8s.io/v1
@@ -275,59 +216,48 @@ spec:
 		})
 	})
 
-	Context("Test Gateway with PluginMetadata", func() {
-		var (
-			err error
-		)
-
-		PIt("Should work OK with error-page", func() {
-			By("Update GatewayProxy with PluginMetadata")
-			err = s.CreateResourceFromString(fmt.Sprintf(gatewayProxyWithPluginMetadata0, s.Deployer.GetAdminEndpoint(), s.AdminKey()))
-			Expect(err).ShouldNot(HaveOccurred())
+	Context("Test GatewayProxy with invalid endpoint", func() {
+		var gatewayProxyWithInvalidEndpoint = `
+apiVersion: apisix.apache.org/v1alpha1
+kind: GatewayProxy
+metadata:
+  name: apisix-proxy-config
+spec:
+  provider:
+    type: ControlPlane
+    controlPlane:
+      endpoints:
+        - "http://invalid-endpoint:9180"
+        - %s
+      auth:
+        type: AdminKey
+        adminKey:
+          value: "%s"
+`
+		It("Should fail to apply GatewayProxy with invalid endpoint", func() {
+			By("Update GatewayProxy with invalid endpoint")
+			err := s.CreateResourceFromString(fmt.Sprintf(gatewayProxyWithInvalidEndpoint, s.Deployer.GetAdminEndpoint(), s.AdminKey()))
+			Expect(err).NotTo(HaveOccurred(), "creating GatewayProxy with enabled plugin")
 			time.Sleep(5 * time.Second)
 
-			By("Create HTTPRoute for Gateway with GatewayProxy")
+			By("Create HTTPRoute")
 			resourceApplied("HTTPRoute", "test-route", fmt.Sprintf(httpRouteForTest, "apisix"), 1)
 
-			time.Sleep(5 * time.Second)
-			By("Check PluginMetadata working")
-			s.NewAPISIXClient().
-				GET("/not-found").
-				WithHost("example.com").
-				Expect().
-				Status(http.StatusNotFound).
-				Body().Contains("404 from plugin metadata")
-
-			By("Update GatewayProxy with PluginMetadata")
-			err = s.CreateResourceFromString(fmt.Sprintf(gatewayProxyWithPluginMetadata1, s.Deployer.GetAdminEndpoint(), s.AdminKey()))
-			Expect(err).ShouldNot(HaveOccurred())
-			time.Sleep(5 * time.Second)
-
-			By("Check PluginMetadata working")
-			s.NewAPISIXClient().
-				GET("/not-found").
-				WithHost("example.com").
-				Expect().
-				Status(http.StatusNotFound).
-				Body().Contains(`{"error_msg":"404 Route Not Found"}`)
-
-			By("Delete GatewayProxy")
-			err = s.DeleteResourceFromString(fmt.Sprintf(gatewayProxyWithPluginMetadata0, s.Deployer.GetAdminEndpoint(), s.AdminKey()))
-			Expect(err).ShouldNot(HaveOccurred())
-			time.Sleep(5 * time.Second)
+			expectRequest := func() bool {
+				resp := s.NewAPISIXClient().
+					GET("/get").
+					WithHost("example.com").
+					Expect().Raw()
+				return resp.StatusCode == 200 && resp.Header.Get("X-Proxy-Test") == ""
+			}
 
-			By("Check PluginMetadata is not working")
-			s.NewAPISIXClient().
-				GET("/not-found").
-				WithHost("example.com").
-				Expect().
-				Status(http.StatusNotFound).
-				Body().Contains(`{"error_msg":"404 Route Not Found"}`)
+			Eventually(expectRequest).WithTimeout(8 * time.Second).ProbeEvery(time.Second).Should(BeTrue())
 		})
 	})
 
-	var (
-		gatewayProxyWithInvalidProviderType = `
+	Context("Test GatewayProxy Provider Validation", func() {
+		var (
+			gatewayProxyWithInvalidProviderType = `
 apiVersion: apisix.apache.org/v1alpha1
 kind: GatewayProxy
 metadata:
@@ -336,7 +266,7 @@ spec:
   provider:
     type: "InvalidType"
 `
-		gatewayProxyWithMissingControlPlane = `
+			gatewayProxyWithMissingControlPlane = `
 apiVersion: apisix.apache.org/v1alpha1
 kind: GatewayProxy
 metadata:
@@ -345,7 +275,7 @@ spec:
   provider:
     type: "ControlPlane"
 `
-		gatewayProxyWithValidProvider = `
+			gatewayProxyWithValidProvider = `
 apiVersion: apisix.apache.org/v1alpha1
 kind: GatewayProxy
 metadata:
@@ -361,9 +291,7 @@ spec:
         adminKey:
           value: "test-key"
 `
-	)
-
-	Context("Test GatewayProxy Provider Validation", func() {
+		)
 		It("Should reject invalid provider type", func() {
 			By("Create GatewayProxy with invalid provider type")
 			err := s.CreateResourceFromString(gatewayProxyWithInvalidProviderType)


(apisix-ingress-controller) 04/04: fix: apisixroute backend service reference to apisixupstream (#2453)

Posted by al...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

alinsran pushed a commit to branch v2.0.0
in repository https://gitbox.apache.org/repos/asf/apisix-ingress-controller.git

commit 43bbe76801dd0b1243221277b91fc713bac1d157
Author: 悟空 <ra...@163.com>
AuthorDate: Sat Jul 5 10:02:00 2025 +0800

    fix: apisixroute backend service reference to apisixupstream (#2453)
---
 internal/controller/apisixroute_controller.go   | 60 ++++++++++----------
 internal/controller/indexer/indexer.go          |  2 +-
 internal/provider/adc/translator/apisixroute.go | 64 ++++++++++-----------
 test/e2e/apisix/route.go                        | 75 +++++++++++++++++++++++--
 test/e2e/apiv2/apisixconsumer.go                | 18 ------
 test/e2e/apiv2/apisixglobalrule.go              | 18 ------
 test/e2e/apiv2/apisixpluginconfig.go            | 18 ------
 test/e2e/apiv2/apisixroute.go                   | 18 ------
 test/e2e/apiv2/apisixtls.go                     | 18 ------
 test/e2e/apiv2/apisixupstream.go                | 18 ------
 10 files changed, 130 insertions(+), 179 deletions(-)

diff --git a/internal/controller/apisixroute_controller.go b/internal/controller/apisixroute_controller.go
index 99fee292..bce27913 100644
--- a/internal/controller/apisixroute_controller.go
+++ b/internal/controller/apisixroute_controller.go
@@ -24,7 +24,9 @@ import (
 	"fmt"
 	"slices"
 
+	"github.com/api7/gopkg/pkg/log"
 	"github.com/go-logr/logr"
+	"go.uber.org/zap"
 	corev1 "k8s.io/api/core/v1"
 	discoveryv1 "k8s.io/api/discovery/v1"
 	networkingv1 "k8s.io/api/networking/v1"
@@ -283,13 +285,12 @@ func (r *ApisixRouteReconciler) validateBackends(ctx context.Context, tc *provid
 	var backends = make(map[types.NamespacedName]struct{})
 	for _, backend := range http.Backends {
 		var (
-			service = corev1.Service{
-				ObjectMeta: metav1.ObjectMeta{
-					Name:      backend.ServiceName,
-					Namespace: in.Namespace,
-				},
+			au        apiv2.ApisixUpstream
+			service   corev1.Service
+			serviceNN = types.NamespacedName{
+				Namespace: in.GetNamespace(),
+				Name:      backend.ServiceName,
 			}
-			serviceNN = utils.NamespacedName(&service)
 		)
 		if _, ok := backends[serviceNN]; ok {
 			return ReasonError{
@@ -300,12 +301,24 @@ func (r *ApisixRouteReconciler) validateBackends(ctx context.Context, tc *provid
 		backends[serviceNN] = struct{}{}
 
 		if err := r.Get(ctx, serviceNN, &service); err != nil {
-			if err := client.IgnoreNotFound(err); err == nil {
+			if err = client.IgnoreNotFound(err); err == nil {
 				r.Log.Error(errors.New("service not found"), "Service", serviceNN)
 				continue
 			}
 			return err
 		}
+
+		// try to get apisixupstream with the same name as the backend service
+		log.Debugw("try to get apisixupstream with the same name as the backend service", zap.Stringer("Service", serviceNN))
+		if err := r.Get(ctx, serviceNN, &au); err != nil {
+			log.Debugw("no ApisixUpstream with the same name as the backend service found", zap.Stringer("Service", serviceNN), zap.Error(err))
+			if err = client.IgnoreNotFound(err); err != nil {
+				return err
+			}
+		} else {
+			tc.Upstreams[serviceNN] = &au
+		}
+
 		if service.Spec.Type == corev1.ServiceTypeExternalName {
 			tc.Services[serviceNN] = &service
 			continue
@@ -339,11 +352,7 @@ func (r *ApisixRouteReconciler) validateBackends(ctx context.Context, tc *provid
 
 		// backend.subset specifies a subset of upstream nodes.
 		// It specifies that the target pod's label should be a superset of the subset labels of the ApisixUpstream of the serviceName
-		subsetLabels, err := r.getSubsetLabels(ctx, in, backend)
-		if err != nil {
-			return err
-		}
-
+		subsetLabels := r.getSubsetLabels(tc, serviceNN, backend)
 		tc.EndpointSlices[serviceNN] = r.filterEndpointSlicesBySubsetLabels(ctx, endpoints.Items, subsetLabels)
 	}
 
@@ -512,7 +521,7 @@ func (r *ApisixRouteReconciler) listApisixRouteForApisixUpstream(ctx context.Con
 
 	var arList apiv2.ApisixRouteList
 	if err := r.List(ctx, &arList, client.MatchingFields{indexer.ApisixUpstreamRef: indexer.GenIndexKey(au.GetNamespace(), au.GetName())}); err != nil {
-		r.Log.Error(err, "failed to list ApisixUpstreams")
+		r.Log.Error(err, "failed to list ApisixRoutes")
 		return nil
 	}
 
@@ -569,35 +578,24 @@ func (r *ApisixRouteReconciler) listApisixRoutesForPluginConfig(ctx context.Cont
 	return pkgutils.DedupComparable(requests)
 }
 
-func (r *ApisixRouteReconciler) getSubsetLabels(ctx context.Context, ar *apiv2.ApisixRoute, backend apiv2.ApisixRouteHTTPBackend) (map[string]string, error) {
-	empty := make(map[string]string)
+func (r *ApisixRouteReconciler) getSubsetLabels(tctx *provider.TranslateContext, auNN types.NamespacedName, backend apiv2.ApisixRouteHTTPBackend) map[string]string {
 	if backend.Subset == "" {
-		return empty, nil
+		return nil
 	}
 
-	// Try to Get the ApisixUpstream with the same name as backend.ServiceName
-	var (
-		auNN = types.NamespacedName{
-			Namespace: ar.GetNamespace(),
-			Name:      backend.ServiceName,
-		}
-		au apiv2.ApisixUpstream
-	)
-	if err := r.Get(ctx, auNN, &au); err != nil {
-		if client.IgnoreNotFound(err) == nil {
-			return empty, nil
-		}
-		return nil, err
+	au, ok := tctx.Upstreams[auNN]
+	if !ok {
+		return nil
 	}
 
 	// try to get the subset labels from the ApisixUpstream subsets
 	for _, subset := range au.Spec.Subsets {
 		if backend.Subset == subset.Name {
-			return subset.Labels, nil
+			return subset.Labels
 		}
 	}
 
-	return empty, nil
+	return nil
 }
 
 func (r *ApisixRouteReconciler) filterEndpointSlicesBySubsetLabels(ctx context.Context, in []discoveryv1.EndpointSlice, labels map[string]string) []discoveryv1.EndpointSlice {
diff --git a/internal/controller/indexer/indexer.go b/internal/controller/indexer/indexer.go
index d9dd1638..2f1b7574 100644
--- a/internal/controller/indexer/indexer.go
+++ b/internal/controller/indexer/indexer.go
@@ -568,7 +568,7 @@ func ApisixRouteApisixUpstreamIndexFunc(obj client.Object) (keys []string) {
 	ar := obj.(*apiv2.ApisixRoute)
 	for _, rule := range ar.Spec.HTTP {
 		for _, backend := range rule.Backends {
-			if backend.Subset != "" && backend.ServiceName != "" {
+			if backend.ServiceName != "" {
 				keys = append(keys, GenIndexKey(ar.GetNamespace(), backend.ServiceName))
 			}
 		}
diff --git a/internal/provider/adc/translator/apisixroute.go b/internal/provider/adc/translator/apisixroute.go
index f8348d6a..c622202e 100644
--- a/internal/provider/adc/translator/apisixroute.go
+++ b/internal/provider/adc/translator/apisixroute.go
@@ -195,28 +195,35 @@ func (t *Translator) buildRoute(ar *apiv2.ApisixRoute, service *adc.Service, rul
 
 func (t *Translator) buildUpstream(tctx *provider.TranslateContext, service *adc.Service, ar *apiv2.ApisixRoute, rule apiv2.ApisixRouteHTTP) {
 	var (
-		upstream          = adc.NewDefaultUpstream()
 		upstreams         = make([]*adc.Upstream, 0)
 		weightedUpstreams = make([]adc.TrafficSplitConfigRuleWeightedUpstream, 0)
 		backendErr        error
 	)
 
 	for _, backend := range rule.Backends {
-		var upNodes adc.UpstreamNodes
+		upstream := adc.NewDefaultUpstream()
+		// try to get the apisixupstream with the same name as the backend service to be upstream config.
+		// err is ignored because it does not care about the externalNodes of the apisixupstream.
+		auNN := types.NamespacedName{Namespace: ar.GetNamespace(), Name: backend.ServiceName}
+		if au, ok := tctx.Upstreams[auNN]; ok {
+			upstream, _ = t.translateApisixUpstream(tctx, au)
+		}
+
 		if backend.ResolveGranularity == "service" {
-			upNodes, backendErr = t.translateApisixRouteBackendResolveGranularityService(tctx, utils.NamespacedName(ar), backend)
+			upstream.Nodes, backendErr = t.translateApisixRouteBackendResolveGranularityService(tctx, utils.NamespacedName(ar), backend)
 			if backendErr != nil {
 				t.Log.Error(backendErr, "failed to translate ApisixRoute backend with ResolveGranularity Service")
 				continue
 			}
 		} else {
-			upNodes, backendErr = t.translateApisixRouteBackendResolveGranularityEndpoint(tctx, utils.NamespacedName(ar), backend)
+			upstream.Nodes, backendErr = t.translateApisixRouteBackendResolveGranularityEndpoint(tctx, utils.NamespacedName(ar), backend)
 			if backendErr != nil {
 				t.Log.Error(backendErr, "failed to translate ApisixRoute backend with ResolveGranularity Endpoint")
 				continue
 			}
 		}
-		upstream.Nodes = append(upstream.Nodes, upNodes...)
+
+		upstreams = append(upstreams, upstream)
 	}
 
 	for _, upstreamRef := range rule.Upstreams {
@@ -241,21 +248,26 @@ func (t *Translator) buildUpstream(tctx *provider.TranslateContext, service *adc
 		upstreams = append(upstreams, upstream)
 	}
 
-	// If no .http[].backends is used and only .http[].upstreams is used, the first valid upstream is used as service.upstream;
-	// Other upstreams are configured in the traffic-split plugin
-	if len(rule.Backends) == 0 && len(upstreams) > 0 {
-		upstream = upstreams[0]
-		upstreams = upstreams[1:]
+	// no valid upstream
+	if backendErr != nil || len(upstreams) == 0 || len(upstreams[0].Nodes) == 0 {
+		return
 	}
 
-	// set the default upstream's weight in traffic-split
-	weight, err := strconv.Atoi(upstream.Labels["meta_weight"])
-	if err != nil {
-		weight = apiv2.DefaultWeight
+	// the first valid upstream is used as service.upstream;
+	// the others are configured in the traffic-split plugin
+	service.Upstream = upstreams[0]
+	upstreams = upstreams[1:]
+
+	// set weight in traffic-split for the default upstream
+	if len(upstreams) > 0 {
+		weight, err := strconv.Atoi(service.Upstream.Labels["meta_weight"])
+		if err != nil {
+			weight = apiv2.DefaultWeight
+		}
+		weightedUpstreams = append(weightedUpstreams, adc.TrafficSplitConfigRuleWeightedUpstream{
+			Weight: weight,
+		})
 	}
-	weightedUpstreams = append(weightedUpstreams, adc.TrafficSplitConfigRuleWeightedUpstream{
-		Weight: weight,
-	})
 
 	// set others upstreams in traffic-split
 	for _, item := range upstreams {
@@ -269,11 +281,6 @@ func (t *Translator) buildUpstream(tctx *provider.TranslateContext, service *adc
 		})
 	}
 
-	// set service
-	service.Upstream = upstream
-	if backendErr != nil && len(upstream.Nodes) == 0 {
-		t.addFaultInjectionPlugin(service)
-	}
 	if len(weightedUpstreams) > 0 {
 		service.Plugins["traffic-split"] = &adc.TrafficSplitConfig{
 			Rules: []adc.TrafficSplitConfigRule{
@@ -291,21 +298,10 @@ func (t *Translator) buildService(ar *apiv2.ApisixRoute, rule apiv2.ApisixRouteH
 	service.ID = id.GenID(service.Name)
 	service.Labels = label.GenLabel(ar)
 	service.Hosts = rule.Match.Hosts
+	service.Upstream = adc.NewDefaultUpstream()
 	return service
 }
 
-func (t *Translator) addFaultInjectionPlugin(service *adc.Service) {
-	if service.Plugins == nil {
-		service.Plugins = make(map[string]any)
-	}
-	service.Plugins["fault-injection"] = map[string]any{
-		"abort": map[string]any{
-			"http_status": 500,
-			"body":        "No existing backendRef provided",
-		},
-	}
-}
-
 func (t *Translator) translateApisixRouteBackendResolveGranularityService(tctx *provider.TranslateContext, arNN types.NamespacedName, backend apiv2.ApisixRouteHTTPBackend) (adc.UpstreamNodes, error) {
 	serviceNN := types.NamespacedName{
 		Namespace: arNN.Namespace,
diff --git a/test/e2e/apisix/route.go b/test/e2e/apisix/route.go
index 12940bd0..a47e6a31 100644
--- a/test/e2e/apisix/route.go
+++ b/test/e2e/apisix/route.go
@@ -18,6 +18,7 @@
 package apisix
 
 import (
+	"context"
 	"fmt"
 	"net"
 	"net/http"
@@ -26,6 +27,7 @@ import (
 	. "github.com/onsi/ginkgo/v2"
 	. "github.com/onsi/gomega"
 	"k8s.io/apimachinery/pkg/types"
+	"k8s.io/apimachinery/pkg/util/wait"
 
 	apiv2 "github.com/apache/apisix-ingress-controller/api/v2"
 	"github.com/apache/apisix-ingress-controller/test/e2e/framework"
@@ -247,11 +249,7 @@ spec:
 			var apisixRoute apiv2.ApisixRoute
 			applier.MustApplyAPIv2(types.NamespacedName{Namespace: s.Namespace(), Name: "default"}, &apisixRoute, fmt.Sprintf(apisixRouteSpec, "/get"))
 
-			By("when there is no replica got 500 by fault-injection")
-			err := s.ScaleHTTPBIN(0)
-			Expect(err).ShouldNot(HaveOccurred(), "scale httpbin to 0")
-			Eventually(request).WithArguments("/get").WithTimeout(8 * time.Second).ProbeEvery(time.Second).Should(Equal(http.StatusInternalServerError))
-			s.NewAPISIXClient().GET("/get").WithHost("httpbin").Expect().Body().IsEqual("No existing backendRef provided")
+			Eventually(request).WithArguments("/get").WithTimeout(8 * time.Second).ProbeEvery(time.Second).Should(Equal(http.StatusServiceUnavailable))
 		})
 
 		It("Test ApisixRoute resolveGranularity", func() {
@@ -501,5 +499,72 @@ spec:
 			Eventually(upstreamAddrs).Should(HaveKey(endpoint))
 			Eventually(upstreamAddrs).Should(HaveKey(clusterIP))
 		})
+
+		It("Test backend implicit reference to apisixupstream", func() {
+			var err error
+
+			const apisixRouteSpec = `
+apiVersion: apisix.apache.org/v2
+kind: ApisixRoute
+metadata:
+  name: default
+spec:
+  ingressClassName: apisix
+  http:
+  - name: rule0
+    match:
+      hosts:
+      - httpbin
+      paths:
+      - /*
+    backends:
+    - serviceName: httpbin-service-e2e-test
+      servicePort: 80
+    plugins:
+    - name: response-rewrite
+      enable: true
+      config:
+        headers:
+          set:
+            "X-Upstream-Host": "$upstream_host"
+
+`
+			const apisixUpstreamSpec = `
+apiVersion: apisix.apache.org/v2
+kind: ApisixUpstream
+metadata:
+  name: httpbin-service-e2e-test
+spec:
+  ingressClassName: apisix
+  passHost: rewrite
+  upstreamHost: hello.httpbin.org
+  loadbalancer:
+    type: "chash"
+    hashOn: "vars"
+    key: "server_name"
+`
+			expectUpstreamHostIs := func(expectedUpstreamHost string) func(ctx context.Context) (bool, error) {
+				return func(ctx context.Context) (done bool, err error) {
+					resp := s.NewAPISIXClient().GET("/get").WithHost("httpbin").Expect().Raw()
+					return resp.StatusCode == http.StatusOK && resp.Header.Get("X-Upstream-Host") == expectedUpstreamHost, nil
+				}
+			}
+
+			By("apply apisixroute")
+			applier.MustApplyAPIv2(types.NamespacedName{Namespace: s.Namespace(), Name: "default"}, new(apiv2.ApisixRoute), apisixRouteSpec)
+
+			By("verify ApisixRoute works")
+			// expect upstream host is "httpbin"
+			err = wait.PollUntilContextTimeout(context.Background(), time.Second, 10*time.Second, true, expectUpstreamHostIs("httpbin"))
+			Expect(err).ShouldNot(HaveOccurred(), "verify ApisixRoute works")
+
+			By("apply apisixupstream")
+			applier.MustApplyAPIv2(types.NamespacedName{Namespace: s.Namespace(), Name: "httpbin-service-e2e-test"}, new(apiv2.ApisixUpstream), apisixUpstreamSpec)
+
+			By("verify backend implicit reference to apisixupstream works")
+			// expect upstream host is "hello.httpbin.org" which is rewritten by the apisixupstream
+			err = wait.PollUntilContextTimeout(context.Background(), time.Second, 10*time.Second, true, expectUpstreamHostIs("hello.httpbin.org"))
+			Expect(err).ShouldNot(HaveOccurred(), "check apisixupstream is referenced")
+		})
 	})
 })
diff --git a/test/e2e/apiv2/apisixconsumer.go b/test/e2e/apiv2/apisixconsumer.go
deleted file mode 100644
index 97cba5b3..00000000
--- a/test/e2e/apiv2/apisixconsumer.go
+++ /dev/null
@@ -1,18 +0,0 @@
-// Licensed to the Apache Software Foundation (ASF) under one
-// or more contributor license agreements.  See the NOTICE file
-// distributed with this work for additional information
-// regarding copyright ownership.  The ASF licenses this file
-// to you under the Apache License, Version 2.0 (the
-// "License"); you may not use this file except in compliance
-// with the License.  You may obtain a copy of the License at
-//
-//   http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing,
-// software distributed under the License is distributed on an
-// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-// KIND, either express or implied.  See the License for the
-// specific language governing permissions and limitations
-// under the License.
-
-package apiv2
diff --git a/test/e2e/apiv2/apisixglobalrule.go b/test/e2e/apiv2/apisixglobalrule.go
deleted file mode 100644
index 97cba5b3..00000000
--- a/test/e2e/apiv2/apisixglobalrule.go
+++ /dev/null
@@ -1,18 +0,0 @@
-// Licensed to the Apache Software Foundation (ASF) under one
-// or more contributor license agreements.  See the NOTICE file
-// distributed with this work for additional information
-// regarding copyright ownership.  The ASF licenses this file
-// to you under the Apache License, Version 2.0 (the
-// "License"); you may not use this file except in compliance
-// with the License.  You may obtain a copy of the License at
-//
-//   http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing,
-// software distributed under the License is distributed on an
-// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-// KIND, either express or implied.  See the License for the
-// specific language governing permissions and limitations
-// under the License.
-
-package apiv2
diff --git a/test/e2e/apiv2/apisixpluginconfig.go b/test/e2e/apiv2/apisixpluginconfig.go
deleted file mode 100644
index 97cba5b3..00000000
--- a/test/e2e/apiv2/apisixpluginconfig.go
+++ /dev/null
@@ -1,18 +0,0 @@
-// Licensed to the Apache Software Foundation (ASF) under one
-// or more contributor license agreements.  See the NOTICE file
-// distributed with this work for additional information
-// regarding copyright ownership.  The ASF licenses this file
-// to you under the Apache License, Version 2.0 (the
-// "License"); you may not use this file except in compliance
-// with the License.  You may obtain a copy of the License at
-//
-//   http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing,
-// software distributed under the License is distributed on an
-// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-// KIND, either express or implied.  See the License for the
-// specific language governing permissions and limitations
-// under the License.
-
-package apiv2
diff --git a/test/e2e/apiv2/apisixroute.go b/test/e2e/apiv2/apisixroute.go
deleted file mode 100644
index 97cba5b3..00000000
--- a/test/e2e/apiv2/apisixroute.go
+++ /dev/null
@@ -1,18 +0,0 @@
-// Licensed to the Apache Software Foundation (ASF) under one
-// or more contributor license agreements.  See the NOTICE file
-// distributed with this work for additional information
-// regarding copyright ownership.  The ASF licenses this file
-// to you under the Apache License, Version 2.0 (the
-// "License"); you may not use this file except in compliance
-// with the License.  You may obtain a copy of the License at
-//
-//   http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing,
-// software distributed under the License is distributed on an
-// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-// KIND, either express or implied.  See the License for the
-// specific language governing permissions and limitations
-// under the License.
-
-package apiv2
diff --git a/test/e2e/apiv2/apisixtls.go b/test/e2e/apiv2/apisixtls.go
deleted file mode 100644
index 97cba5b3..00000000
--- a/test/e2e/apiv2/apisixtls.go
+++ /dev/null
@@ -1,18 +0,0 @@
-// Licensed to the Apache Software Foundation (ASF) under one
-// or more contributor license agreements.  See the NOTICE file
-// distributed with this work for additional information
-// regarding copyright ownership.  The ASF licenses this file
-// to you under the Apache License, Version 2.0 (the
-// "License"); you may not use this file except in compliance
-// with the License.  You may obtain a copy of the License at
-//
-//   http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing,
-// software distributed under the License is distributed on an
-// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-// KIND, either express or implied.  See the License for the
-// specific language governing permissions and limitations
-// under the License.
-
-package apiv2
diff --git a/test/e2e/apiv2/apisixupstream.go b/test/e2e/apiv2/apisixupstream.go
deleted file mode 100644
index 97cba5b3..00000000
--- a/test/e2e/apiv2/apisixupstream.go
+++ /dev/null
@@ -1,18 +0,0 @@
-// Licensed to the Apache Software Foundation (ASF) under one
-// or more contributor license agreements.  See the NOTICE file
-// distributed with this work for additional information
-// regarding copyright ownership.  The ASF licenses this file
-// to you under the Apache License, Version 2.0 (the
-// "License"); you may not use this file except in compliance
-// with the License.  You may obtain a copy of the License at
-//
-//   http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing,
-// software distributed under the License is distributed on an
-// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-// KIND, either express or implied.  See the License for the
-// specific language governing permissions and limitations
-// under the License.
-
-package apiv2


(apisix-ingress-controller) 01/04: fix: support filter endpoint when translate backend ref. (#2451)

Posted by al...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

alinsran pushed a commit to branch v2.0.0
in repository https://gitbox.apache.org/repos/asf/apisix-ingress-controller.git

commit d8be46e71934beec746356e105f1fad2e5a3a580
Author: Ashing Zheng <ax...@gmail.com>
AuthorDate: Fri Jul 4 14:10:06 2025 +0800

    fix: support filter endpoint when translate backend ref. (#2451)
    
    Signed-off-by: ashing <ax...@gmail.com>
---
 internal/provider/adc/config.go                 |  9 ++++++++-
 internal/provider/adc/translator/apisixroute.go |  2 +-
 internal/provider/adc/translator/httproute.go   | 23 +++++++++++++++++------
 internal/provider/adc/translator/ingress.go     |  3 +++
 4 files changed, 29 insertions(+), 8 deletions(-)

diff --git a/internal/provider/adc/config.go b/internal/provider/adc/config.go
index 2bf512d3..ec6d94fc 100644
--- a/internal/provider/adc/config.go
+++ b/internal/provider/adc/config.go
@@ -26,6 +26,7 @@ import (
 
 	"github.com/api7/gopkg/pkg/log"
 	"go.uber.org/zap"
+	discoveryv1 "k8s.io/api/discovery/v1"
 	k8stypes "k8s.io/apimachinery/pkg/types"
 	"k8s.io/utils/ptr"
 	gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
@@ -95,12 +96,18 @@ func (d *adcClient) getConfigsForGatewayProxy(tctx *provider.TranslateContext, g
 		if endpoint == nil {
 			return nil, nil
 		}
-		upstreamNodes, err := d.translator.TranslateBackendRef(tctx, gatewayv1.BackendRef{
+		upstreamNodes, err := d.translator.TranslateBackendRefWithFilter(tctx, gatewayv1.BackendRef{
 			BackendObjectReference: gatewayv1.BackendObjectReference{
 				Name:      gatewayv1.ObjectName(provider.ControlPlane.Service.Name),
 				Namespace: (*gatewayv1.Namespace)(&gatewayProxy.Namespace),
 				Port:      ptr.To(gatewayv1.PortNumber(provider.ControlPlane.Service.Port)),
 			},
+		}, func(endpoint *discoveryv1.Endpoint) bool {
+			if endpoint.Conditions.Terminating != nil && *endpoint.Conditions.Terminating {
+				log.Debugw("skip terminating endpoint", zap.Any("endpoint", endpoint))
+				return false
+			}
+			return true
 		})
 		if err != nil {
 			return nil, err
diff --git a/internal/provider/adc/translator/apisixroute.go b/internal/provider/adc/translator/apisixroute.go
index 2f1b7dd8..f8348d6a 100644
--- a/internal/provider/adc/translator/apisixroute.go
+++ b/internal/provider/adc/translator/apisixroute.go
@@ -339,5 +339,5 @@ func (t *Translator) translateApisixRouteBackendResolveGranularityEndpoint(tctx
 		},
 		Weight: &weight,
 	}
-	return t.translateBackendRef(tctx, backendRef)
+	return t.translateBackendRef(tctx, backendRef, DefaultEndpointFilter)
 }
diff --git a/internal/provider/adc/translator/httproute.go b/internal/provider/adc/translator/httproute.go
index d1c66b71..32cb9d8e 100644
--- a/internal/provider/adc/translator/httproute.go
+++ b/internal/provider/adc/translator/httproute.go
@@ -283,7 +283,7 @@ func (t *Translator) fillHTTPRoutePolicies(routes []*adctypes.Route, policies []
 	}
 }
 
-func (t *Translator) translateEndpointSlice(portName *string, weight int, endpointSlices []discoveryv1.EndpointSlice) adctypes.UpstreamNodes {
+func (t *Translator) translateEndpointSlice(portName *string, weight int, endpointSlices []discoveryv1.EndpointSlice, endpointFilter func(*discoveryv1.Endpoint) bool) adctypes.UpstreamNodes {
 	var nodes adctypes.UpstreamNodes
 	if len(endpointSlices) == 0 {
 		return nodes
@@ -294,6 +294,9 @@ func (t *Translator) translateEndpointSlice(portName *string, weight int, endpoi
 				continue
 			}
 			for _, endpoint := range endpointSlice.Endpoints {
+				if endpointFilter != nil && !endpointFilter(&endpoint) {
+					continue
+				}
 				for _, addr := range endpoint.Addresses {
 					node := adctypes.UpstreamNode{
 						Host:   addr,
@@ -312,11 +315,19 @@ func (t *Translator) translateEndpointSlice(portName *string, weight int, endpoi
 	return nodes
 }
 
-func (t *Translator) TranslateBackendRef(tctx *provider.TranslateContext, ref gatewayv1.BackendRef) (adctypes.UpstreamNodes, error) {
-	return t.translateBackendRef(tctx, ref)
+func DefaultEndpointFilter(endpoint *discoveryv1.Endpoint) bool {
+	if endpoint.Conditions.Ready != nil && !*endpoint.Conditions.Ready {
+		log.Debugw("skip not ready endpoint", zap.Any("endpoint", endpoint))
+		return false
+	}
+	return true
+}
+
+func (t *Translator) TranslateBackendRefWithFilter(tctx *provider.TranslateContext, ref gatewayv1.BackendRef, endpointFilter func(*discoveryv1.Endpoint) bool) (adctypes.UpstreamNodes, error) {
+	return t.translateBackendRef(tctx, ref, endpointFilter)
 }
 
-func (t *Translator) translateBackendRef(tctx *provider.TranslateContext, ref gatewayv1.BackendRef) (adctypes.UpstreamNodes, error) {
+func (t *Translator) translateBackendRef(tctx *provider.TranslateContext, ref gatewayv1.BackendRef, endpointFilter func(*discoveryv1.Endpoint) bool) (adctypes.UpstreamNodes, error) {
 	if ref.Kind != nil && *ref.Kind != "Service" {
 		return adctypes.UpstreamNodes{}, fmt.Errorf("kind %s is not supported", *ref.Kind)
 	}
@@ -363,7 +374,7 @@ func (t *Translator) translateBackendRef(tctx *provider.TranslateContext, ref ga
 	}
 
 	endpointSlices := tctx.EndpointSlices[key]
-	return t.translateEndpointSlice(portName, weight, endpointSlices), nil
+	return t.translateEndpointSlice(portName, weight, endpointSlices, endpointFilter), nil
 }
 
 // calculateHTTPRoutePriority calculates the priority of the HTTP route.
@@ -475,7 +486,7 @@ func (t *Translator) TranslateHTTPRoute(tctx *provider.TranslateContext, httpRou
 				namespace := gatewayv1.Namespace(httpRoute.Namespace)
 				backend.Namespace = &namespace
 			}
-			upNodes, err := t.translateBackendRef(tctx, backend.BackendRef)
+			upNodes, err := t.translateBackendRef(tctx, backend.BackendRef, DefaultEndpointFilter)
 			if err != nil {
 				backendErr = err
 				continue
diff --git a/internal/provider/adc/translator/ingress.go b/internal/provider/adc/translator/ingress.go
index 60a48204..7293558b 100644
--- a/internal/provider/adc/translator/ingress.go
+++ b/internal/provider/adc/translator/ingress.go
@@ -234,6 +234,9 @@ func (t *Translator) translateEndpointSliceForIngress(weight int, endpointSlices
 				continue
 			}
 			for _, endpoint := range endpointSlice.Endpoints {
+				if !DefaultEndpointFilter(&endpoint) {
+					continue
+				}
 				for _, addr := range endpoint.Addresses {
 					node := adctypes.UpstreamNode{
 						Host:   addr,