You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@apisix.apache.org by ro...@apache.org on 2025/09/04 07:20:33 UTC

(apisix-ingress-controller) branch master updated: fix: handle httproute multi backend refs (#2540)

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

ronething 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 ec8624b1 fix: handle httproute multi backend refs (#2540)
ec8624b1 is described below

commit ec8624b11d5c71d6904c6f9b292b260794db3e5f
Author: Ashing Zheng <ax...@gmail.com>
AuthorDate: Thu Sep 4 15:20:28 2025 +0800

    fix: handle httproute multi backend refs (#2540)
    
    Signed-off-by: Ashing Zheng <ax...@gmail.com>
    Co-authored-by: Copilot <17...@users.noreply.github.com>
---
 go.mod                                  |  4 +-
 internal/adc/translator/httproute.go    | 80 ++++++++++++++++++++++++++++-----
 test/e2e/framework/manifests/nginx.yaml |  2 +-
 test/e2e/framework/nginx.go             |  1 +
 test/e2e/gatewayapi/httproute.go        |  2 +
 5 files changed, 75 insertions(+), 14 deletions(-)

diff --git a/go.mod b/go.mod
index 6cd78f73..11483ab9 100644
--- a/go.mod
+++ b/go.mod
@@ -10,7 +10,9 @@ require (
 	github.com/gavv/httpexpect/v2 v2.16.0
 	github.com/go-logr/logr v1.4.2
 	github.com/go-logr/zapr v1.3.0
+	github.com/google/go-cmp v0.6.0
 	github.com/google/uuid v1.6.0
+	github.com/gorilla/websocket v1.5.1
 	github.com/gruntwork-io/terratest v0.50.0
 	github.com/hashicorp/go-memdb v1.3.4
 	github.com/incubator4/go-resty-expr v0.1.1
@@ -108,11 +110,9 @@ require (
 	github.com/golang/protobuf v1.5.4 // indirect
 	github.com/google/cel-go v0.20.1 // indirect
 	github.com/google/gnostic-models v0.6.8 // indirect
-	github.com/google/go-cmp v0.6.0 // indirect
 	github.com/google/go-querystring v1.1.0 // indirect
 	github.com/google/gofuzz v1.2.0 // indirect
 	github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 // indirect
-	github.com/gorilla/websocket v1.5.1 // indirect
 	github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect
 	github.com/gruntwork-io/go-commons v0.8.0 // indirect
 	github.com/hashicorp/errwrap v1.1.0 // indirect
diff --git a/internal/adc/translator/httproute.go b/internal/adc/translator/httproute.go
index 4c3f6426..90816258 100644
--- a/internal/adc/translator/httproute.go
+++ b/internal/adc/translator/httproute.go
@@ -33,6 +33,7 @@ import (
 
 	adctypes "github.com/apache/apisix-ingress-controller/api/adc"
 	"github.com/apache/apisix-ingress-controller/api/v1alpha1"
+	apiv2 "github.com/apache/apisix-ingress-controller/api/v2"
 	"github.com/apache/apisix-ingress-controller/internal/controller/label"
 	"github.com/apache/apisix-ingress-controller/internal/id"
 	"github.com/apache/apisix-ingress-controller/internal/provider"
@@ -466,32 +467,89 @@ func (t *Translator) TranslateHTTPRoute(tctx *provider.TranslateContext, httpRou
 	labels := label.GenLabel(httpRoute)
 
 	for ruleIndex, rule := range rules {
-		upstream := adctypes.NewDefaultUpstream()
-		var backendErr error
+		service := adctypes.NewDefaultService()
+		service.Labels = labels
+
+		service.Name = adctypes.ComposeServiceNameWithRule(httpRoute.Namespace, httpRoute.Name, fmt.Sprintf("%d", ruleIndex))
+		service.ID = id.GenID(service.Name)
+		service.Hosts = hosts
+
+		var (
+			upstreams         = make([]*adctypes.Upstream, 0)
+			weightedUpstreams = make([]adctypes.TrafficSplitConfigRuleWeightedUpstream, 0)
+			backendErr        error
+		)
+
 		for _, backend := range rule.BackendRefs {
 			if backend.Namespace == nil {
 				namespace := gatewayv1.Namespace(httpRoute.Namespace)
 				backend.Namespace = &namespace
 			}
+			upstream := adctypes.NewDefaultUpstream()
 			upNodes, err := t.translateBackendRef(tctx, backend.BackendRef, DefaultEndpointFilter)
 			if err != nil {
 				backendErr = err
 				continue
 			}
+			if len(upNodes) == 0 {
+				continue
+			}
+
 			t.AttachBackendTrafficPolicyToUpstream(backend.BackendRef, tctx.BackendTrafficPolicies, upstream)
-			upstream.Nodes = append(upstream.Nodes, upNodes...)
+			upstream.Nodes = upNodes
+			upstreams = append(upstreams, upstream)
 		}
 
-		// todo: support multiple backends
-		service := adctypes.NewDefaultService()
-		service.Labels = labels
+		// Handle multiple backends with traffic-split plugin
+		if len(upstreams) == 0 {
+			// Create a default upstream if no valid backends
+			upstream := adctypes.NewDefaultUpstream()
+			service.Upstream = upstream
+		} else if len(upstreams) == 1 {
+			// Single backend - use directly as service upstream
+			service.Upstream = upstreams[0]
+		} else {
+			// Multiple backends - use traffic-split plugin
+			service.Upstream = upstreams[0]
+			upstreams = upstreams[1:]
+
+			// Set weight in traffic-split for the default upstream
+			weight := apiv2.DefaultWeight
+			if rule.BackendRefs[0].Weight != nil {
+				weight = int(*rule.BackendRefs[0].Weight)
+			}
+			weightedUpstreams = append(weightedUpstreams, adctypes.TrafficSplitConfigRuleWeightedUpstream{
+				Weight: weight,
+			})
 
-		service.Name = adctypes.ComposeServiceNameWithRule(httpRoute.Namespace, httpRoute.Name, fmt.Sprintf("%d", ruleIndex))
-		service.ID = id.GenID(service.Name)
-		service.Hosts = hosts
-		service.Upstream = upstream
+			// Set other upstreams in traffic-split
+			for i, upstream := range upstreams {
+				weight := apiv2.DefaultWeight
+				// get weight from the backend refs starting from the second backend
+				if i+1 < len(rule.BackendRefs) && rule.BackendRefs[i+1].Weight != nil {
+					weight = int(*rule.BackendRefs[i+1].Weight)
+				}
+				weightedUpstreams = append(weightedUpstreams, adctypes.TrafficSplitConfigRuleWeightedUpstream{
+					Upstream: upstream,
+					Weight:   weight,
+				})
+			}
+
+			if len(weightedUpstreams) > 0 {
+				if service.Plugins == nil {
+					service.Plugins = make(map[string]any)
+				}
+				service.Plugins["traffic-split"] = &adctypes.TrafficSplitConfig{
+					Rules: []adctypes.TrafficSplitConfigRule{
+						{
+							WeightedUpstreams: weightedUpstreams,
+						},
+					},
+				}
+			}
+		}
 
-		if backendErr != nil && len(upstream.Nodes) == 0 {
+		if backendErr != nil && (service.Upstream == nil || len(service.Upstream.Nodes) == 0) {
 			if service.Plugins == nil {
 				service.Plugins = make(map[string]any)
 			}
diff --git a/test/e2e/framework/manifests/nginx.yaml b/test/e2e/framework/manifests/nginx.yaml
index c0d97373..7fb93f08 100644
--- a/test/e2e/framework/manifests/nginx.yaml
+++ b/test/e2e/framework/manifests/nginx.yaml
@@ -44,7 +44,7 @@ kind: Deployment
 metadata:
   name: nginx
 spec:
-  replicas: 1
+  replicas: {{ .Replicas | default 1 }}
   selector:
     matchLabels:
       app: nginx
diff --git a/test/e2e/framework/nginx.go b/test/e2e/framework/nginx.go
index ae179597..9612c4e3 100644
--- a/test/e2e/framework/nginx.go
+++ b/test/e2e/framework/nginx.go
@@ -37,6 +37,7 @@ var (
 
 type NginxOptions struct {
 	Namespace string
+	Replicas  *int32
 }
 
 func init() {
diff --git a/test/e2e/gatewayapi/httproute.go b/test/e2e/gatewayapi/httproute.go
index 9bdbc7b8..77b50849 100644
--- a/test/e2e/gatewayapi/httproute.go
+++ b/test/e2e/gatewayapi/httproute.go
@@ -30,6 +30,7 @@ import (
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/types"
+	"k8s.io/utils/ptr"
 	"sigs.k8s.io/gateway-api/apis/v1alpha2"
 
 	"github.com/apache/apisix-ingress-controller/api/v1alpha1"
@@ -1916,6 +1917,7 @@ spec:
 			beforeEachHTTP()
 			s.DeployNginx(framework.NginxOptions{
 				Namespace: s.Namespace(),
+				Replicas:  ptr.To(int32(2)),
 			})
 		})
 		It("HTTPRoute Canary", func() {